Files
foundry-core/internal/platform/asset_preview_test.go
T
codex-bot 8abc79dc5b
Foundry Go checks / check (push) Successful in 5m13s
Foundry Go checks / check (pull_request) Successful in 3m56s
Keep a single preview variant and verify read lock boundaries
2026-11-06 21:43:48 +08:00

138 lines
4.5 KiB
Go

package platform
import (
"bytes"
"context"
"encoding/binary"
"image"
"image/color"
"image/jpeg"
"image/png"
"testing"
)
func TestAssetPreviewDownscalesWithoutChangingOriginal(t *testing.T) {
source := image.NewNRGBA(image.Rect(0, 0, 1600, 900))
for y := 0; y < 900; y++ {
for x := 0; x < 1600; x++ {
source.SetNRGBA(x, y, color.NRGBA{uint8(x), uint8(y), uint8(x * y), 255})
}
}
var encoded bytes.Buffer
if err := png.Encode(&encoded, source); err != nil {
t.Fatal(err)
}
original := append([]byte(nil), encoded.Bytes()...)
preview, err := makeAssetPreview(encoded.Bytes(), "image/png", 512)
if err != nil {
t.Fatal(err)
}
config, format, err := image.DecodeConfig(bytes.NewReader(preview.data))
if err != nil || config.Width != 512 || config.Height != 288 || format != "jpeg" {
t.Fatalf("bad preview %s %dx%d %v", format, config.Width, config.Height, err)
}
if len(preview.data) >= len(original) || !bytes.Equal(original, encoded.Bytes()) {
t.Fatal("preview must shrink without changing original")
}
cached, err := cachedAssetPreview(context.Background(), original, "image/png", 512)
if err != nil || !bytes.Equal(cached.data, preview.data) {
t.Fatal("cache result mismatch")
}
}
func TestAssetPreviewPreservesTransparencyAndAnimation(t *testing.T) {
source := image.NewNRGBA(image.Rect(0, 0, 800, 400))
source.SetNRGBA(1, 1, color.NRGBA{255, 0, 0, 255})
var encoded bytes.Buffer
_ = png.Encode(&encoded, source)
preview, err := makeAssetPreview(encoded.Bytes(), "image/png", 256)
if err != nil {
t.Fatal(err)
}
decoded, format, err := image.Decode(bytes.NewReader(preview.data))
if err != nil || format != "png" {
t.Fatal("transparent image must stay PNG")
}
_, _, _, alpha := decoded.At(200, 100).RGBA()
if alpha != 0 {
t.Fatal("transparent background was lost")
}
original := []byte("GIF original animation bytes")
gif, err := makeAssetPreview(original, "image/gif", 512)
if err != nil || !bytes.Equal(gif.data, original) {
t.Fatal("GIF animation changed")
}
}
func TestAssetPreviewRejectsUnboundedWidths(t *testing.T) {
for _, value := range []string{"0", "1", "256", "512", "2048", "999999", "-1", "abc"} {
if _, err := previewWidth("preview", value); err == nil {
t.Errorf("accepted width %s", value)
}
}
for _, value := range []string{"", "1024"} {
if _, err := previewWidth("preview", value); err != nil {
t.Fatal(err)
}
}
if n, err := previewWidth("", ""); err != nil || n != 0 {
t.Fatal("original request changed")
}
if _, err := previewWidth("other", ""); err == nil {
t.Fatal("unknown variant accepted")
}
if _, err := makeAssetPreview([]byte("invalid"), "image/png", 512); err == nil {
t.Fatal("malformed image accepted")
}
}
func TestAssetPreviewAppliesJPEGOrientation(t *testing.T) {
source := image.NewNRGBA(image.Rect(0, 0, 800, 400))
for y := 0; y < 400; y++ {
for x := 0; x < 800; x++ {
source.SetNRGBA(x, y, color.NRGBA{255, 0, 0, 255})
}
}
var original bytes.Buffer
_ = jpeg.Encode(&original, source, nil)
tiff := make([]byte, 26)
copy(tiff, "II")
binary.LittleEndian.PutUint16(tiff[2:], 42)
binary.LittleEndian.PutUint32(tiff[4:], 8)
binary.LittleEndian.PutUint16(tiff[8:], 1)
binary.LittleEndian.PutUint16(tiff[10:], 0x112)
binary.LittleEndian.PutUint16(tiff[12:], 3)
binary.LittleEndian.PutUint32(tiff[14:], 1)
binary.LittleEndian.PutUint16(tiff[18:], 6)
exif := append([]byte("Exif\x00\x00"), tiff...)
segment := append([]byte{0xff, 0xe1, 0, byte(len(exif) + 2)}, exif...)
encoded := append(append(append([]byte{}, original.Bytes()[:2]...), segment...), original.Bytes()[2:]...)
if jpegOrientation(encoded) != 6 {
t.Fatal("EXIF orientation not decoded")
}
preview, err := makeAssetPreview(encoded, "image/jpeg", 256)
if err != nil {
t.Fatal(err)
}
config, _, err := image.DecodeConfig(bytes.NewReader(preview.data))
if err != nil || config.Width != 128 || config.Height != 256 {
t.Fatalf("rotated dimensions %dx%d %v", config.Width, config.Height, err)
}
}
func TestAllAssetOrientationMappings(t *testing.T) {
source := image.NewNRGBA(image.Rect(0, 0, 3, 2))
source.SetNRGBA(0, 0, color.NRGBA{255, 0, 0, 255})
for orientation := 1; orientation <= 8; orientation++ {
oriented := orientedAsset{source, orientation}
red := 0
for y := 0; y < oriented.Bounds().Dy(); y++ {
for x := 0; x < oriented.Bounds().Dx(); x++ {
r, _, _, _ := oriented.At(x, y).RGBA()
if r > 0 {
red++
}
}
}
if red != 1 {
t.Errorf("orientation %d loses/repeats pixels", orientation)
}
}
}