diff options
| author | ivar <i@oiee.no> | 2026-04-07 00:23:24 +0200 |
|---|---|---|
| committer | ivar <i@oiee.no> | 2026-04-07 00:23:24 +0200 |
| commit | 85920b8c7a2696115d1f77c046f48f6f00d639f1 (patch) | |
| tree | 14ed2043796eadd6ed5b0a95c55e38e48713d638 /internal/media/handler_test.go | |
| download | iblog-85920b8c7a2696115d1f77c046f48f6f00d639f1.tar.xz iblog-85920b8c7a2696115d1f77c046f48f6f00d639f1.zip | |
Init
Diffstat (limited to 'internal/media/handler_test.go')
| -rw-r--r-- | internal/media/handler_test.go | 198 |
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) + } + } +} |
