package service import ( "bytes" "image" "image/color" "image/gif" "image/jpeg" "image/png" "os" "path/filepath" "strings" "testing" "github.com/freefire/jiang13-bbs/model" webpenc "github.com/gen2brain/webp" "github.com/glebarez/sqlite" xwebp "golang.org/x/image/webp" "gorm.io/gorm" ) func newUploadTestService(t *testing.T) (*UploadService, string) { t.Helper() db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) if err != nil { t.Fatalf("open sqlite: %v", err) } if err := db.AutoMigrate(&model.Attachment{}); err != nil { t.Fatalf("migrate: %v", err) } dir := t.TempDir() s := NewUploadService(db, dir) if err := s.EnsureDir(); err != nil { t.Fatalf("ensure dir: %v", err) } return s, dir } func storedUploadPath(t *testing.T, dir, url string) []byte { t.Helper() rel := strings.TrimPrefix(url, "/uploads/") data, err := os.ReadFile(filepath.Join(dir, filepath.FromSlash(rel))) if err != nil { t.Fatalf("read stored file %s: %v", url, err) } return data } func TestSaveImageTranscodesJPEGToWebP(t *testing.T) { s, dir := newUploadTestService(t) src := image.NewRGBA(image.Rect(0, 0, 48, 36)) for y := 0; y < 36; y++ { for x := 0; x < 48; x++ { src.Set(x, y, color.RGBA{uint8(x * 5), uint8(y * 7), 128, 255}) } } var in bytes.Buffer if err := jpeg.Encode(&in, src, nil); err != nil { t.Fatalf("encode jpeg: %v", err) } att, err := s.SaveImage(1, bytes.NewReader(in.Bytes())) if err != nil { t.Fatalf("SaveImage: %v", err) } if !strings.HasSuffix(att.URL, ".webp") || att.MIME != "image/webp" { t.Fatalf("want webp attachment, got url=%s mime=%s", att.URL, att.MIME) } if att.Width != 48 || att.Height != 36 { t.Fatalf("dims changed: %dx%d", att.Width, att.Height) } img, err := xwebp.Decode(bytes.NewReader(storedUploadPath(t, dir, att.URL))) if err != nil { t.Fatalf("decode stored webp: %v", err) } if got := img.Bounds(); got.Dx() != 48 || got.Dy() != 36 { t.Fatalf("stored webp dims: %v", got) } } func TestSaveImageTranscodesPNGLosslessKeepsAlpha(t *testing.T) { s, dir := newUploadTestService(t) src := image.NewNRGBA(image.Rect(0, 0, 24, 24)) for y := 0; y < 24; y++ { for x := 0; x < 24; x++ { if y < 12 { src.SetNRGBA(x, y, color.NRGBA{0, 0, 0, 0}) // δΈŠεŠι€ζ˜Ž } else { src.SetNRGBA(x, y, color.NRGBA{12, 34, 56, 255}) } } } var in bytes.Buffer if err := png.Encode(&in, src); err != nil { t.Fatalf("encode png: %v", err) } att, err := s.SaveImage(1, bytes.NewReader(in.Bytes())) if err != nil { t.Fatalf("SaveImage: %v", err) } if !strings.HasSuffix(att.URL, ".webp") || att.MIME != "image/webp" { t.Fatalf("want webp attachment, got url=%s mime=%s", att.URL, att.MIME) } img, err := xwebp.Decode(bytes.NewReader(storedUploadPath(t, dir, att.URL))) if err != nil { t.Fatalf("decode stored webp: %v", err) } if _, _, _, a := img.At(0, 0).RGBA(); a != 0 { t.Fatalf("alpha lost at transparent pixel: %d", a) } r, g, b, _ := img.At(5, 20).RGBA() if r>>8 != 12 || g>>8 != 34 || b>>8 != 56 { t.Fatalf("opaque color changed: %d,%d,%d", r>>8, g>>8, b>>8) } } func TestSaveImageKeepsGIFAsIs(t *testing.T) { s, dir := newUploadTestService(t) src := image.NewRGBA(image.Rect(0, 0, 16, 16)) for y := 0; y < 16; y++ { for x := 0; x < 16; x++ { src.Set(x, y, color.RGBA{uint8(x * 16), uint8(y * 16), 0, 255}) } } var in bytes.Buffer if err := gif.Encode(&in, src, nil); err != nil { t.Fatalf("encode gif: %v", err) } att, err := s.SaveImage(1, bytes.NewReader(in.Bytes())) if err != nil { t.Fatalf("SaveImage: %v", err) } if !strings.HasSuffix(att.URL, ".gif") || att.MIME != "image/gif" { t.Fatalf("gif should be kept as-is, got url=%s mime=%s", att.URL, att.MIME) } data := storedUploadPath(t, dir, att.URL) magic := string(data[:6]) if magic != "GIF89a" && magic != "GIF87a" { t.Fatalf("stored gif magic invalid: %q", magic) } } func TestSaveImageKeepsWebPAsIs(t *testing.T) { s, dir := newUploadTestService(t) src := image.NewRGBA(image.Rect(0, 0, 20, 10)) var in bytes.Buffer if err := webpenc.Encode(&in, src, webpenc.Options{Quality: ImageWebPQuality}); err != nil { t.Fatalf("encode webp: %v", err) } att, err := s.SaveImage(1, bytes.NewReader(in.Bytes())) if err != nil { t.Fatalf("SaveImage: %v", err) } if !strings.HasSuffix(att.URL, ".webp") || att.MIME != "image/webp" { t.Fatalf("want webp attachment, got url=%s mime=%s", att.URL, att.MIME) } if !bytes.Equal(storedUploadPath(t, dir, att.URL), in.Bytes()) { t.Fatal("webp input should be stored byte-for-byte") } if att.Size != in.Len() { t.Fatalf("size mismatch: att=%d input=%d", att.Size, in.Len()) } }