Files
Crussell/backend/internal/images/validate_test.go
T
popertotsandSisyphus 0da7498ad7 feat(images): multi-format portfolio pipeline (AVIF/WebP/JPEG/JXL)
Backend now stores AVIF, WebP, JPEG, and optional JXL variants for both full-size and thumbnail images. Database schema extended with 7 new columns. Image validation supports AVIF and JXL magic bytes. Comprehensive test coverage for all format URL fields and magic byte detection. Legacy single-URL images remain backward-compatible.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-06-04 01:06:33 +01:00

267 lines
8.6 KiB
Go

//go:build test
// +build test
package images
import (
"bytes"
"testing"
)
// TestValidateImageBytes_JPEG verifies that JPEG magic bytes (FF D8 FF) are detected.
func TestValidateImageBytes_JPEG(t *testing.T) {
data := []byte{0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46}
ext, err := ValidateImageBytes(data)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if ext != ".jpg" {
t.Errorf("expected .jpg, got %q", ext)
}
}
// TestValidateImageBytes_PNG verifies that PNG magic bytes are detected.
func TestValidateImageBytes_PNG(t *testing.T) {
data := []byte{0x89, 'P', 'N', 'G', '\r', '\n', 0x1a, '\n', 0x00, 0x00, 0x00, 0x0d}
ext, err := ValidateImageBytes(data)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if ext != ".png" {
t.Errorf("expected .png, got %q", ext)
}
}
// TestValidateImageBytes_GIF87a verifies GIF87a magic bytes are detected.
func TestValidateImageBytes_GIF87a(t *testing.T) {
data := []byte{'G', 'I', 'F', '8', '7', 'a', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
ext, err := ValidateImageBytes(data)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if ext != ".gif" {
t.Errorf("expected .gif, got %q", ext)
}
}
// TestValidateImageBytes_GIF89a verifies GIF89a magic bytes are detected.
func TestValidateImageBytes_GIF89a(t *testing.T) {
data := []byte{'G', 'I', 'F', '8', '9', 'a', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
ext, err := ValidateImageBytes(data)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if ext != ".gif" {
t.Errorf("expected .gif, got %q", ext)
}
}
// TestValidateImageBytes_WebP verifies RIFF...WEBP magic bytes are detected.
func TestValidateImageBytes_WebP(t *testing.T) {
data := []byte{'R', 'I', 'F', 'F', 0x00, 0x00, 0x00, 0x00, 'W', 'E', 'B', 'P'}
ext, err := ValidateImageBytes(data)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if ext != ".webp" {
t.Errorf("expected .webp, got %q", ext)
}
}
// TestValidateImageBytes_AVIF verifies ftypavif magic bytes are detected.
func TestValidateImageBytes_AVIF(t *testing.T) {
data := []byte{0x00, 0x00, 0x00, 0x20, 'f', 't', 'y', 'p', 'a', 'v', 'i', 'f'}
ext, err := ValidateImageBytes(data)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if ext != ".avif" {
t.Errorf("expected .avif, got %q", ext)
}
}
// TestValidateImageBytes_AVIS verifies ftypavis (AVIF image sequence) magic bytes are detected.
func TestValidateImageBytes_AVIS(t *testing.T) {
data := []byte{0x00, 0x00, 0x00, 0x20, 'f', 't', 'y', 'p', 'a', 'v', 'i', 's'}
ext, err := ValidateImageBytes(data)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if ext != ".avif" {
t.Errorf("expected .avif, got %q", ext)
}
}
// TestValidateImageBytes_JXL verifies ftypjxl magic bytes are detected.
func TestValidateImageBytes_JXL(t *testing.T) {
data := []byte{0x00, 0x00, 0x00, 0x0c, 'f', 't', 'y', 'p', 'j', 'x', 'l', ' '}
ext, err := ValidateImageBytes(data)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if ext != ".jxl" {
t.Errorf("expected .jxl, got %q", ext)
}
}
// TestValidateImageBytes_TooSmall verifies that files smaller than 12 bytes are rejected.
func TestValidateImageBytes_TooSmall(t *testing.T) {
data := []byte{0xff, 0xd8, 0xff}
_, err := ValidateImageBytes(data)
if err == nil {
t.Fatal("expected error for too-small file, got nil")
}
}
// TestValidateImageBytes_UnknownFormat verifies that unrecognized magic bytes return an error.
func TestValidateImageBytes_UnknownFormat(t *testing.T) {
data := []byte("this is not an image file at all!!")
_, err := ValidateImageBytes(data)
if err == nil {
t.Fatal("expected error for unknown format, got nil")
}
}
// TestValidateImageBytes_Exact12Bytes verifies the boundary condition of exactly 12 bytes.
func TestValidateImageBytes_Exact12Bytes(t *testing.T) {
// Valid JPEG header truncated to exactly 12 bytes (should still match 3-byte magic)
data := []byte{0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01}
ext, err := ValidateImageBytes(data)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if ext != ".jpg" {
t.Errorf("expected .jpg, got %q", ext)
}
}
// TestValidateImageBytes_WebPNotRIFF verifies that WEBP without RIFF prefix is rejected.
func TestValidateImageBytes_WebPNotRIFF(t *testing.T) {
data := []byte{'W', 'E', 'B', 'P', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
_, err := ValidateImageBytes(data)
if err == nil {
t.Fatal("expected error for WEBP without RIFF prefix, got nil")
}
}
// TestValidateImageBytes_AVIFWrongBrand verifies that ftyp with wrong brand is rejected.
func TestValidateImageBytes_AVIFWrongBrand(t *testing.T) {
// ftyp with "mp41" brand (not avif/avis)
data := []byte{0x00, 0x00, 0x00, 0x20, 'f', 't', 'y', 'p', 'm', 'p', '4', '1'}
_, err := ValidateImageBytes(data)
if err == nil {
t.Fatal("expected error for non-AVIF ftyp, got nil")
}
}
// TestValidateImageBytes_Empty verifies that empty input is rejected.
func TestValidateImageBytes_Empty(t *testing.T) {
data := []byte{}
_, err := ValidateImageBytes(data)
if err == nil {
t.Fatal("expected error for empty input, got nil")
}
}
// TestValidateImageBytes_AllSupportedFormats verifies every supported format returns a non-empty extension.
func TestValidateImageBytes_AllSupportedFormats(t *testing.T) {
tests := []struct {
name string
data []byte
wantExt string
wantErr bool
}{
{"JPEG", []byte{0xff, 0xd8, 0xff, 0x00}, ".jpg", false},
{"PNG", []byte{0x89, 'P', 'N', 'G', '\r', '\n', 0x1a, '\n', 0, 0, 0, 0}, ".png", false},
{"GIF87a", []byte{'G', 'I', 'F', '8', '7', 'a', 0, 0, 0, 0, 0, 0}, ".gif", false},
{"GIF89a", []byte{'G', 'I', 'F', '8', '9', 'a', 0, 0, 0, 0, 0, 0}, ".gif", false},
{"WebP", []byte{'R', 'I', 'F', 'F', 0, 0, 0, 0, 'W', 'E', 'B', 'P'}, ".webp", false},
{"AVIF", []byte{0, 0, 0, 0x20, 'f', 't', 'y', 'p', 'a', 'v', 'i', 'f'}, ".avif", false},
{"AVIS", []byte{0, 0, 0, 0x20, 'f', 't', 'y', 'p', 'a', 'v', 'i', 's'}, ".avif", false},
{"JXL", []byte{0, 0, 0, 0x0c, 'f', 't', 'y', 'p', 'j', 'x', 'l', ' '}, ".jxl", false},
{"Random", []byte("not-an-image-file!"), "", true},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
ext, err := ValidateImageBytes(tc.data)
if tc.wantErr {
if err == nil {
t.Errorf("expected error, got nil")
}
} else {
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if ext != tc.wantExt {
t.Errorf("expected %q, got %q", tc.wantExt, ext)
}
}
})
}
}
// TestValidateImageBytes_NoFalsePositiveJPEG verifies that data starting with FF D8 but not FF D8 FF is rejected.
func TestValidateImageBytes_NoFalsePositiveJPEG(t *testing.T) {
data := []byte{0xff, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
_, err := ValidateImageBytes(data)
if err == nil {
t.Fatal("expected error for non-JPEG starting with FF D8, got nil")
}
}
// TestValidateImageBytes_PartialMagic verifies that partial magic bytes (e.g., only first 2 bytes of PNG) are rejected.
func TestValidateImageBytes_PartialMagic(t *testing.T) {
// Only first 2 bytes of PNG signature
data := []byte{0x89, 'P', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
_, err := ValidateImageBytes(data)
if err == nil {
t.Fatal("expected error for partial PNG magic, got nil")
}
}
// BenchmarkValidateImageBytes measures validation throughput.
func BenchmarkValidateImageBytes(b *testing.B) {
data := []byte{0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = ValidateImageBytes(data)
}
}
// BenchmarkValidateImageBytes_AVIF measures AVIF detection throughput.
func BenchmarkValidateImageBytes_AVIF(b *testing.B) {
data := []byte{0x00, 0x00, 0x00, 0x20, 'f', 't', 'y', 'p', 'a', 'v', 'i', 'f'}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = ValidateImageBytes(data)
}
}
// BenchmarkValidateImageBytes_WebP measures WebP detection throughput.
func BenchmarkValidateImageBytes_WebP(b *testing.B) {
data := []byte{'R', 'I', 'F', 'F', 0x00, 0x00, 0x00, 0x00, 'W', 'E', 'B', 'P'}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = ValidateImageBytes(data)
}
}
// BenchmarkValidateImageBytes_JXL measures JXL detection throughput.
func BenchmarkValidateImageBytes_JXL(b *testing.B) {
data := []byte{0x00, 0x00, 0x00, 0x0c, 'f', 't', 'y', 'p', 'j', 'x', 'l', ' '}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = ValidateImageBytes(data)
}
}
// TestValidateImageBytes_11Bytes verifies that 11-byte files (just under the 12-byte minimum) are rejected.
func TestValidateImageBytes_11Bytes(t *testing.T) {
data := bytes.Repeat([]byte{0x00}, 11)
_, err := ValidateImageBytes(data)
if err == nil {
t.Fatal("expected error for 11-byte file, got nil")
}
}