Files
jiang13-bbs/backend/service/upload_webp_test.go
freefire ff2ab286fb feat: 书库导入导出/图片变体/书籍搜索/小组件运行时等
新增:
- 书库导入导出(library_import/library_export)及测试
- 图片变体生成(image_variants)与响应式图片(responsiveImage)
- 书籍搜索(bookSearch)+ BookSearch/LibrarySearchGrid 组件
- 小组件运行时(widgetRuntime)与静态检查(widgetLint)
- 上传缓存中间件(upload_cache)与字体 CSS 提取脚本

其它:
- 后端 handlers/services 全量调整
- 前端页面、组件、库函数与配置更新
2026-10-01 03:06:05 +08:00

173 lines
4.6 KiB
Go

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()), "post")
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()), "post")
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()), "post")
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()), "post")
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())
}
}