// fileread_webp_test.go -- WebP EXIF 方向校正测试. package builtin import ( "bytes" "encoding/binary" "image" "testing" ) // ───────────────────────────────────────────────────────────────────── // 辅助:构建最小 WebP RIFF with VP8X + EXIF chunk // ───────────────────────────────────────────────────────────────────── // buildWebPWithEXIF 构建一个包含 EXIF chunk 的最小 Extended WebP. // 注意:图片内容是无效的(VP8X 后没有 VP8/VP8L 数据), // 但足够测试 EXIF 解析逻辑. func buildWebPWithEXIF(orient exifOrientation) []byte { // 构建 EXIF chunk 数据(Exif\0\0 + TIFF) exifData := buildMinimalEXIF(orient) // 复用 fileread_exif_test.go 中的函数 // VP8X chunk(10 字节 payload) // flags byte: bit3=EXIF, bit5=Alpha... // 我们只设置 EXIF bit(bit3=1 → 0x08) vp8xPayload := make([]byte, 10) vp8xPayload[0] = 0x08 // EXIF bit // 构建 RIFF 容器 var body bytes.Buffer // VP8X chunk body.Write([]byte("VP8X")) _ = binary.Write(&body, binary.LittleEndian, uint32(10)) body.Write(vp8xPayload) // EXIF chunk body.Write([]byte("EXIF")) _ = binary.Write(&body, binary.LittleEndian, uint32(len(exifData))) body.Write(exifData) // 对齐到 2 字节 if len(exifData)%2 != 0 { body.WriteByte(0x00) } // RIFF header var out bytes.Buffer out.Write([]byte("RIFF")) _ = binary.Write(&out, binary.LittleEndian, uint32(4+body.Len())) // "WEBP" + body out.Write([]byte("WEBP")) out.Write(body.Bytes()) return out.Bytes() } // ───────────────────────────────────────────────────────────────────── // parseWebPOrientation 测试 // ───────────────────────────────────────────────────────────────────── func TestParseWebPOrientation_Normal(t *testing.T) { data := buildWebPWithEXIF(exifOrientNormal) got := parseWebPOrientation(data) if got != exifOrientNormal { t.Errorf("got %d, want %d", got, exifOrientNormal) } } func TestParseWebPOrientation_Rotate90CW(t *testing.T) { data := buildWebPWithEXIF(exifOrientRotate90CW) got := parseWebPOrientation(data) if got != exifOrientRotate90CW { t.Errorf("got %d, want %d", got, exifOrientRotate90CW) } } func TestParseWebPOrientation_AllValues(t *testing.T) { for _, orient := range []exifOrientation{1, 2, 3, 4, 5, 6, 7, 8} { data := buildWebPWithEXIF(orient) got := parseWebPOrientation(data) if got != orient { t.Errorf("orient=%d: got %d", orient, got) } } } func TestParseWebPOrientation_NoEXIF(t *testing.T) { // 构建无 EXIF chunk 的 WebP(Simple VP8 头) data := make([]byte, 20) copy(data[0:4], "RIFF") binary.LittleEndian.PutUint32(data[4:8], 12) copy(data[8:12], "WEBP") copy(data[12:16], "VP8 ") binary.LittleEndian.PutUint32(data[16:20], 0) got := parseWebPOrientation(data) if got != 1 { t.Errorf("无 EXIF chunk 应返回 1,got %d", got) } } func TestParseWebPOrientation_NotWebP(t *testing.T) { got := parseWebPOrientation([]byte("not a webp")) if got != 1 { t.Errorf("非 WebP 应返回 1,got %d", got) } } func TestParseWebPOrientation_TooShort(t *testing.T) { got := parseWebPOrientation([]byte{0xFF, 0xD8}) if got != 1 { t.Errorf("太短应返回 1,got %d", got) } } // ───────────────────────────────────────────────────────────────────── // correctWebPOrientation 测试(不解码图片的路径) // ───────────────────────────────────────────────────────────────────── func TestCorrectWebPOrientation_Normal_ReturnOriginal(t *testing.T) { // Orientation=1,内容无效但逻辑上应原样返回(不需要解码) data := buildWebPWithEXIF(exifOrientNormal) result, mt := correctWebPOrientation(data) if !bytes.Equal(result, data) { t.Error("Orientation=1 应原样返回") } if mt != "image/webp" { t.Errorf("期望 image/webp,got %s", mt) } } func TestCorrectWebPOrientation_InvalidData_ReturnOriginal(t *testing.T) { // 无法解码的数据(没有有效的 VP8 图片内容),应 fail-open garbage := []byte{0x00, 0x01, 0x02, 0x03} result, mt := correctWebPOrientation(garbage) if !bytes.Equal(result, garbage) { t.Error("无效数据应原样返回") } if mt != "image/webp" { t.Errorf("期望 image/webp,got %s", mt) } } // ───────────────────────────────────────────────────────────────────── // correctWebPOrientation 全流程测试(需要有效 WebP 图片) // ───────────────────────────────────────────────────────────────────── // buildRealWebPWithOrientation 构建一个真实可解码的 WebP + EXIF. // 使用 Go 测试中的 PNG 内存图片转 WebP(通过 golang.org/x/image/webp Decode 验证). // 注意:Go 没有 WebP encoder,所以只能测试"无旋转的 WebP", // 以验证 correctWebPOrientation 的 fail-open 路径. func TestCorrectWebPOrientation_WithValidWebP(t *testing.T) { // 用 JPEG 数据模拟"无 EXIF 的普通文件"--验证 parseWebPOrientation 返回 1 // 然后 correctWebPOrientation 原样返回 // 真正的 WebP 旋转需要 x/image/webp.Decode 支持(在 correctWebPOrientation 内部已有) // 构建 RIFF/WEBP 头但 EXIF 为 Orientation=1 data := buildWebPWithEXIF(exifOrientNormal) result, mt := correctWebPOrientation(data) // Orientation=1 → 直接返回原始 data,不触发 webp.Decode if !bytes.Equal(result, data) { t.Error("Orientation=1 应原样返回,不解码") } if mt != "image/webp" { t.Errorf("期望 image/webp,got %s", mt) } } // ───────────────────────────────────────────────────────────────────── // detectImageMediaType WebP 测试(补充验证) // ───────────────────────────────────────────────────────────────────── func TestDetectImageMediaType_WebP(t *testing.T) { data := make([]byte, 12) copy(data[0:4], "RIFF") binary.LittleEndian.PutUint32(data[4:8], 4) copy(data[8:12], "WEBP") got := detectImageMediaType(data) if got != "image/webp" { t.Errorf("WebP: got %s", got) } } // ───────────────────────────────────────────────────────────────────── // RIFF chunk 对齐测试 // ───────────────────────────────────────────────────────────────────── func TestParseWebPOrientation_OddSizeEXIF(t *testing.T) { // EXIF 数据奇数字节长度,测试 padding byte 处理正确 // buildMinimalEXIF 返回的长度是偶数,需要构建一个奇数字节的 EXIF exifData := buildMinimalEXIF(exifOrientRotate180) // 截断最后一字节使其奇数(模拟某些编码器) if len(exifData)%2 == 0 { exifData = append(exifData, 0xFF) // 添加一个字节让总长变奇数 } var body bytes.Buffer body.Write([]byte("VP8X")) _ = binary.Write(&body, binary.LittleEndian, uint32(10)) body.Write(make([]byte, 10)) body.Write([]byte("EXIF")) _ = binary.Write(&body, binary.LittleEndian, uint32(len(exifData))) body.Write(exifData) body.WriteByte(0x00) // padding var out bytes.Buffer out.Write([]byte("RIFF")) _ = binary.Write(&out, binary.LittleEndian, uint32(4+body.Len())) out.Write([]byte("WEBP")) out.Write(body.Bytes()) // 只验证不崩溃,orientation 可能解析失败(截断了 TIFF 数据) // 主要测试 pos 步进逻辑正确,不越界 _ = parseWebPOrientation(out.Bytes()) } // 确保 image 包被引用(用于 image.Decode) var _ = image.Rect