summaryrefslogtreecommitdiffstats
path: root/internal/media/handler_test.go
diff options
context:
space:
mode:
authorivar <i@oiee.no>2026-04-07 00:23:24 +0200
committerivar <i@oiee.no>2026-04-07 00:23:24 +0200
commit85920b8c7a2696115d1f77c046f48f6f00d639f1 (patch)
tree14ed2043796eadd6ed5b0a95c55e38e48713d638 /internal/media/handler_test.go
downloadiblog-85920b8c7a2696115d1f77c046f48f6f00d639f1.tar.xz
iblog-85920b8c7a2696115d1f77c046f48f6f00d639f1.zip
Init
Diffstat (limited to 'internal/media/handler_test.go')
-rw-r--r--internal/media/handler_test.go198
1 files changed, 198 insertions, 0 deletions
diff --git a/internal/media/handler_test.go b/internal/media/handler_test.go
new file mode 100644
index 0000000..d65544c
--- /dev/null
+++ b/internal/media/handler_test.go
@@ -0,0 +1,198 @@
+package media
+
+import (
+ "bytes"
+ "encoding/json"
+ "image"
+ "image/jpeg"
+ "mime/multipart"
+ "net/http"
+ "net/http/httptest"
+ "os"
+ "path/filepath"
+ "testing"
+
+ "github.com/gin-gonic/gin"
+)
+
+func init() {
+ gin.SetMode(gin.TestMode)
+}
+
+func newTestHandler(t *testing.T) (*MediaHandler, string) {
+ t.Helper()
+ dir := t.TempDir()
+ return NewMediaHandler(dir), dir
+}
+
+func testJPEGBytes(t *testing.T) []byte {
+ t.Helper()
+ img := image.NewRGBA(image.Rect(0, 0, 100, 60))
+ var buf bytes.Buffer
+ if err := jpeg.Encode(&buf, img, nil); err != nil {
+ t.Fatalf("encode jpeg: %v", err)
+ }
+ return buf.Bytes()
+}
+
+func multipartUpload(t *testing.T, field, filename string, data []byte) *http.Request {
+ t.Helper()
+ var body bytes.Buffer
+ w := multipart.NewWriter(&body)
+ fw, err := w.CreateFormFile(field, filename)
+ if err != nil {
+ t.Fatalf("create form file: %v", err)
+ }
+ fw.Write(data)
+ w.Close()
+ req, _ := http.NewRequest(http.MethodPost, "/admin/upload/image", &body)
+ req.Header.Set("Content-Type", w.FormDataContentType())
+ return req
+}
+
+// TestUploadValid verifies a valid JPEG upload returns success with a /media/ URL.
+func TestUploadValid(t *testing.T) {
+ h, dir := newTestHandler(t)
+ rr := httptest.NewRecorder()
+ c, _ := gin.CreateTestContext(rr)
+ c.Request = multipartUpload(t, "image", "photo.jpg", testJPEGBytes(t))
+
+ h.HandleUpload(c)
+
+ if rr.Code != http.StatusOK {
+ t.Fatalf("expected 200, got %d: %s", rr.Code, rr.Body.String())
+ }
+ var resp map[string]interface{}
+ if err := json.Unmarshal(rr.Body.Bytes(), &resp); err != nil {
+ t.Fatalf("parse response: %v", err)
+ }
+ if resp["success"] != float64(1) {
+ t.Fatalf("expected success=1, got %v", resp)
+ }
+ file, ok := resp["file"].(map[string]interface{})
+ if !ok {
+ t.Fatal("expected file object in response")
+ }
+ url, _ := file["url"].(string)
+ if len(url) < 7 || url[:7] != "/media/" {
+ t.Fatalf("expected /media/ URL, got %q", url)
+ }
+ entries, _ := os.ReadDir(dir)
+ if len(entries) == 0 {
+ t.Fatal("expected original to be written to storage dir")
+ }
+}
+
+// TestUploadInvalidMIME rejects non-image content.
+func TestUploadInvalidMIME(t *testing.T) {
+ h, _ := newTestHandler(t)
+ rr := httptest.NewRecorder()
+ c, _ := gin.CreateTestContext(rr)
+ c.Request = multipartUpload(t, "image", "evil.txt", []byte("not an image at all"))
+
+ h.HandleUpload(c)
+
+ if rr.Code != http.StatusBadRequest {
+ t.Fatalf("expected 400, got %d", rr.Code)
+ }
+}
+
+// TestUploadWrongField rejects request missing the "image" field.
+func TestUploadWrongField(t *testing.T) {
+ h, _ := newTestHandler(t)
+ rr := httptest.NewRecorder()
+ c, _ := gin.CreateTestContext(rr)
+ c.Request = multipartUpload(t, "file", "photo.jpg", testJPEGBytes(t))
+
+ h.HandleUpload(c)
+
+ if rr.Code != http.StatusBadRequest {
+ t.Fatalf("expected 400, got %d", rr.Code)
+ }
+}
+
+// TestServeCacheHit serves a pre-existing cache file directly.
+func TestServeCacheHit(t *testing.T) {
+ h, dir := newTestHandler(t)
+ if err := os.WriteFile(filepath.Join(dir, "abc123.webp"), []byte("FAKE_WEBP"), 0644); err != nil {
+ t.Fatal(err)
+ }
+ rr := httptest.NewRecorder()
+ c, _ := gin.CreateTestContext(rr)
+ c.Request = httptest.NewRequest(http.MethodGet, "/media/abc123.webp", nil)
+ c.Params = gin.Params{{Key: "filepath", Value: "/abc123.webp"}}
+
+ h.HandleServe(c)
+
+ if rr.Code != http.StatusOK {
+ t.Fatalf("expected 200, got %d", rr.Code)
+ }
+ if ct := rr.Header().Get("Content-Type"); ct != "image/webp" {
+ t.Fatalf("expected image/webp, got %q", ct)
+ }
+}
+
+// TestServeNotFound returns 404 when no original exists.
+func TestServeNotFound(t *testing.T) {
+ h, _ := newTestHandler(t)
+ rr := httptest.NewRecorder()
+ c, _ := gin.CreateTestContext(rr)
+ c.Request = httptest.NewRequest(http.MethodGet, "/media/missing.webp", nil)
+ c.Params = gin.Params{{Key: "filepath", Value: "/missing.webp"}}
+
+ h.HandleServe(c)
+
+ if rr.Code != http.StatusNotFound {
+ t.Fatalf("expected 404, got %d", rr.Code)
+ }
+}
+
+// TestServeInvalidWidth returns 400 for non-positive width.
+func TestServeInvalidWidth(t *testing.T) {
+ h, _ := newTestHandler(t)
+ rr := httptest.NewRecorder()
+ c, _ := gin.CreateTestContext(rr)
+ c.Request = httptest.NewRequest(http.MethodGet, "/media/abc.webp?w=0", nil)
+ c.Params = gin.Params{{Key: "filepath", Value: "/abc.webp"}}
+
+ h.HandleServe(c)
+
+ if rr.Code != http.StatusBadRequest {
+ t.Fatalf("expected 400, got %d", rr.Code)
+ }
+}
+
+// TestServeUnknownFormat returns 404 for unrecognised extensions.
+func TestServeUnknownFormat(t *testing.T) {
+ h, _ := newTestHandler(t)
+ rr := httptest.NewRecorder()
+ c, _ := gin.CreateTestContext(rr)
+ c.Request = httptest.NewRequest(http.MethodGet, "/media/abc.png", nil)
+ c.Params = gin.Params{{Key: "filepath", Value: "/abc.png"}}
+
+ h.HandleServe(c)
+
+ if rr.Code != http.StatusNotFound {
+ t.Fatalf("expected 404, got %d", rr.Code)
+ }
+}
+
+// TestCacheKey verifies the cache filename scheme.
+func TestCacheKey(t *testing.T) {
+ cases := []struct {
+ base, format string
+ width int
+ want string
+ }{
+ {"uuid1", "webp", 0, "uuid1.webp"},
+ {"uuid1", "webp", 800, "uuid1_800w.webp"},
+ {"uuid1", "jpeg", 0, "uuid1.jpg"},
+ {"uuid1", "jpeg", 400, "uuid1_400w.jpg"},
+ }
+ for _, tc := range cases {
+ got := cacheKey(tc.base, tc.width, tc.format)
+ if got != tc.want {
+ t.Errorf("cacheKey(%q, %d, %q) = %q, want %q", tc.base, tc.width, tc.format, got, tc.want)
+ }
+ }
+}