Files
pixa/internal/magic/magic_internal_test.go
T
clawbot efbf05a6fa
check / check (push) Waiting to run
Support SVG sources (closes #68)
The fetcher accepts image/svg+xml and internal/magic detects SVG. orig
of an SVG at the orig size is the SVG unchanged, cached as a variant; at
another size it is ErrSVGNotOriginalSize (400). Any other format renders
it with libvips' thumbnail at the least size covering the output, at
most 8192 on a side, so it stays sharp. An SVG's own size is not
limited; one with a side over 8192 times the other is ErrSVGTooThin
(415). The cache reads up to 1024 first bytes to find a file's format.
Tests that pinned the SVG refusal now expect SVG to be read. Render time
and memory are unbounded: P1 in TODO.md until the owner decides on
#68.

Model: opus-5-5
2026-11-11 13:21:12 +00:00

204 lines
5.3 KiB
Go

package magic
import (
"errors"
"testing"
"sneak.berlin/go/pixa/internal/imagerequest"
)
// Shared test fixture strings.
const (
testNameEmpty = "empty"
testMIMEJPEG = "image/jpeg"
testMIMEJPEGParams = "image/jpeg; charset=utf-8"
testMIMEPNG = "image/png"
)
// pad appends zero bytes so data is comfortably above MinMagicBytes.
func pad(b ...byte) []byte {
return append(b, make([]byte, 100)...)
}
func TestDetectFormat(t *testing.T) {
t.Parallel()
jpeg := pad(0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00, 0x01)
png := pad(0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D)
gif87a := pad(0x47, 0x49, 0x46, 0x38, 0x37, 0x61, 0, 0, 0, 0, 0, 0)
gif89a := pad(0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0, 0, 0, 0, 0, 0)
// RIFF + size placeholder + WEBP
webp := pad(0x52, 0x49, 0x46, 0x46, 0, 0, 0, 0, 0x57, 0x45, 0x42, 0x50)
// box size + ftyp + brand
avif := pad(0x00, 0x00, 0x00, 0x1C, 0x66, 0x74, 0x79, 0x70, 0x61, 0x76, 0x69, 0x66)
avis := pad(0x00, 0x00, 0x00, 0x1C, 0x66, 0x74, 0x79, 0x70, 0x61, 0x76, 0x69, 0x73)
tests := []struct {
name string
data []byte
wantFormat imagerequest.Format
wantErr error
}{
{name: "JPEG", data: jpeg, wantFormat: imagerequest.FormatJPEG},
{name: "PNG", data: png, wantFormat: imagerequest.FormatPNG},
{name: "GIF87a", data: gif87a, wantFormat: imagerequest.FormatGIF},
{name: "GIF89a", data: gif89a, wantFormat: imagerequest.FormatGIF},
{name: "WebP", data: webp, wantFormat: imagerequest.FormatWebP},
{name: "AVIF", data: avif, wantFormat: imagerequest.FormatAVIF},
{name: "AVIF sequence", data: avis, wantFormat: imagerequest.FormatAVIF},
{
name: "SVG",
data: []byte(`<svg xmlns="http://www.w3.org/2000/svg"></svg>`),
wantFormat: imagerequest.FormatSVG,
},
{
name: "unknown format",
data: make([]byte, MinMagicBytes),
wantErr: ErrUnknownFormat,
},
{name: "too short", data: []byte{0xFF, 0xD8}, wantErr: ErrNotEnoughData},
{name: testNameEmpty, data: []byte{}, wantErr: ErrNotEnoughData},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got, err := DetectFormat(tt.data)
if !errors.Is(err, tt.wantErr) {
t.Errorf("DetectFormat() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.wantFormat {
t.Errorf("DetectFormat() = %v, want %v", got, tt.wantFormat)
}
})
}
}
func TestValidateMagicBytes(t *testing.T) {
t.Parallel()
jpegData := pad(0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00, 0x01)
pngData := pad(0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D)
tests := []struct {
name string
data []byte
declaredType string
wantFormat imagerequest.Format
wantErr error
}{
{
name: "matching JPEG",
data: jpegData,
declaredType: testMIMEJPEG,
wantFormat: imagerequest.FormatJPEG,
wantErr: nil,
},
{
name: "matching JPEG with params",
data: jpegData,
declaredType: testMIMEJPEGParams,
wantFormat: imagerequest.FormatJPEG,
wantErr: nil,
},
{
name: "matching PNG",
data: pngData,
declaredType: testMIMEPNG,
wantFormat: imagerequest.FormatPNG,
wantErr: nil,
},
{
name: "mismatched type",
data: jpegData,
declaredType: testMIMEPNG,
wantErr: ErrMagicByteMismatch,
},
{
name: "unknown data",
data: make([]byte, MinMagicBytes),
declaredType: testMIMEJPEG,
wantErr: ErrUnknownFormat,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got, err := ValidateMagicBytes(tt.data, tt.declaredType)
if !errors.Is(err, tt.wantErr) {
t.Errorf("ValidateMagicBytes() error = %v, wantErr %v", err, tt.wantErr)
}
if got != tt.wantFormat {
t.Errorf("ValidateMagicBytes() = %q, want %q", got, tt.wantFormat)
}
})
}
}
func TestNormalizeMIMEType(t *testing.T) {
t.Parallel()
tests := []struct {
input string
want string
}{
{testMIMEJPEG, testMIMEJPEG},
{"IMAGE/JPEG", testMIMEJPEG},
{testMIMEJPEGParams, testMIMEJPEG},
{" image/jpeg ", testMIMEJPEG},
{"image/jpeg; boundary=something", testMIMEJPEG},
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
t.Parallel()
got := normalizeMIMEType(tt.input)
if got != tt.want {
t.Errorf("normalizeMIMEType(%q) = %q, want %q", tt.input, got, tt.want)
}
})
}
}
func TestDetectFormatRIFFNotWebP(t *testing.T) {
t.Parallel()
// RIFF container but not WebP (e.g., WAV file)
wavData := []byte{
0x52, 0x49, 0x46, 0x46, // RIFF
0x00, 0x00, 0x00, 0x00, // file size
0x57, 0x41, 0x56, 0x45, // WAVE (not WEBP)
}
_, err := DetectFormat(wavData)
if !errors.Is(err, ErrUnknownFormat) {
t.Errorf("DetectFormat(WAV) error = %v, want %v", err, ErrUnknownFormat)
}
}
func TestDetectFormatFtypNotAVIF(t *testing.T) {
t.Parallel()
// ftyp container but not AVIF (e.g., MP4)
mp4Data := []byte{
0x00, 0x00, 0x00, 0x1C, // box size
0x66, 0x74, 0x79, 0x70, // ftyp
0x69, 0x73, 0x6F, 0x6D, // isom brand (not avif)
}
_, err := DetectFormat(mp4Data)
if !errors.Is(err, ErrUnknownFormat) {
t.Errorf("DetectFormat(MP4) error = %v, want %v", err, ErrUnknownFormat)
}
}