feat: 站点媒体库与移动端底部导航,服务测试补强
- 新增媒体库:media_library/media_thumbs 服务(WebP 缩略图)、管理端 media 页面 - 移动端底部导航 MobileTabBar 替换 MobilePostBar - 旧数据导入增强与测试、路由与登录会话/板块侧边栏测试补强 - site-doc 组件精简(移除 Breadcrumb),文档新增迁移公告说明
This commit is contained in:
172
backend/service/upload_webp_test.go
Normal file
172
backend/service/upload_webp_test.go
Normal file
@@ -0,0 +1,172 @@
|
||||
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())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user