spark-server/internal/infrastructure/media/image_test.go
DESKTOP-L8OUL9F\gfl0034 753c276496 feat: PopAdmin 拉取管理、签到能量 API 与图片 150KiB 硬上限
管理端按用户查询开启通讯录/相册/媒体拉取;服务端写入路径自动压图;新增 /v1/sign 与 /v1/energy。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-02 12:44:06 +08:00

143 lines
3.9 KiB
Go

package media
import (
"bytes"
"image"
"image/color"
"image/jpeg"
"testing"
pkgmedia "spark-server/internal/pkg/media"
)
func TestCompressImageDownscales(t *testing.T) {
img := image.NewRGBA(image.Rect(0, 0, 2000, 1000))
for y := 0; y < 1000; y++ {
for x := 0; x < 2000; x++ {
img.Set(x, y, color.RGBA{R: uint8(x % 255), G: uint8(y % 255), B: 40, A: 255})
}
}
src, err := EncodePNG(img)
if err != nil {
t.Fatal(err)
}
opt := &Options{Limits: pkgmedia.DefaultLimits(), JPEGQuality: 80}
res, err := CompressImage(src, opt)
if err != nil {
t.Fatal(err)
}
if pkgmedia.LongestEdge(res.Width, res.Height) > pkgmedia.DefaultImageMaxEdge {
t.Fatalf("edge %dx%d still over 1080", res.Width, res.Height)
}
if int64(len(res.Data)) > pkgmedia.DefaultImageMaxBytes {
t.Fatalf("size %d > %d", len(res.Data), pkgmedia.DefaultImageMaxBytes)
}
if res.MIME != "image/jpeg" {
t.Fatalf("mime %s", res.MIME)
}
small := image.NewRGBA(image.Rect(0, 0, 100, 80))
smallSrc, err := EncodePNG(small)
if err != nil {
t.Fatal(err)
}
def := &Options{Limits: pkgmedia.DefaultLimits()}
keep, err := CompressImage(smallSrc, def)
if err != nil {
t.Fatal(err)
}
if keep.Width != 100 || keep.Height != 80 {
t.Fatalf("should keep small image dims, got %dx%d", keep.Width, keep.Height)
}
}
func TestCompressImageUnder150KiB(t *testing.T) {
src := mustOversizedJPEG(t, int(pkgmedia.DefaultImageMaxBytes)+1)
res, err := CompressImage(src, DefaultOptions())
if err != nil {
t.Fatal(err)
}
if int64(len(res.Data)) > pkgmedia.DefaultImageMaxBytes {
t.Fatalf("after compress %d > %d", len(res.Data), pkgmedia.DefaultImageMaxBytes)
}
out, mime, err := EnsureUnderLimit(src, "image/jpeg", nil)
if err != nil {
t.Fatal(err)
}
if int64(len(out)) > pkgmedia.DefaultImageMaxBytes {
t.Fatalf("EnsureUnderLimit %d", len(out))
}
if mime != "image/jpeg" {
t.Fatalf("mime %s", mime)
}
}
// mustOversizedJPEG grows a noisy JPEG until len >= minBytes (JPEG of smooth
// gradients can stay tiny; use per-pixel noise and bump resolution as needed).
func mustOversizedJPEG(t *testing.T, minBytes int) []byte {
t.Helper()
for side := 800; side <= 4800; side += 400 {
img := image.NewRGBA(image.Rect(0, 0, side, side*3/4))
w, h := img.Bounds().Dx(), img.Bounds().Dy()
for y := 0; y < h; y++ {
for x := 0; x < w; x++ {
n := uint32(x*374761393 + y*668265263 + side*97)
n ^= n << 13
n ^= n >> 17
n ^= n << 5
img.Set(x, y, color.RGBA{R: uint8(n), G: uint8(n >> 8), B: uint8(n >> 16), A: 255})
}
}
var buf bytes.Buffer
if err := jpeg.Encode(&buf, img, &jpeg.Options{Quality: 100}); err != nil {
t.Fatal(err)
}
if buf.Len() >= minBytes {
return buf.Bytes()
}
}
t.Fatalf("could not synthesize JPEG >= %d bytes", minBytes)
return nil
}
func TestCompressAvatar(t *testing.T) {
img := image.NewRGBA(image.Rect(0, 0, 500, 500))
src, err := EncodePNG(img)
if err != nil {
t.Fatal(err)
}
res, err := CompressAvatar(src, &Options{Limits: pkgmedia.DefaultLimits()})
if err != nil {
t.Fatal(err)
}
if pkgmedia.LongestEdge(res.Width, res.Height) > pkgmedia.DefaultAvatarMaxEdge {
t.Fatalf("avatar %dx%d", res.Width, res.Height)
}
if int64(len(res.Data)) > pkgmedia.DefaultAvatarMaxBytes {
t.Fatalf("avatar size %d", len(res.Data))
}
}
func TestPrepareBotImage(t *testing.T) {
img := image.NewRGBA(image.Rect(0, 0, 1600, 900))
src, err := EncodePNG(img)
if err != nil {
t.Fatal(err)
}
a, err := PrepareBotImage(src, &Options{Limits: pkgmedia.DefaultLimits()})
if err != nil || a == nil || a.Size <= 0 {
t.Fatalf("%+v %v", a, err)
}
if a.Size > pkgmedia.DefaultImageMaxBytes {
t.Fatalf("bot image %d > 150KiB", a.Size)
}
}
func TestIsImageMIME(t *testing.T) {
if !IsImageMIME("image/jpeg") || !IsImageMIME("image/png; charset=binary") {
t.Fatal("want image")
}
if IsImageMIME("audio/mpeg") || IsImageMIME("video/mp4") {
t.Fatal("reject non-image")
}
}