package imagecheck import ( "bytes" "encoding/binary" "errors" "image" "image/color" "image/png" "testing" ) // buildGIFHeader 构造仅含文件头与逻辑屏幕描述符的最小 GIF(无像素数据), // 用于让 image.DecodeConfig 读取出宽高。GIF 无 CRC,构造简单可靠。 func buildGIFHeader(t *testing.T, width, height uint16) []byte { t.Helper() var buf bytes.Buffer buf.WriteString("GIF89a") // Logical Screen Descriptor: width(2 LE) + height(2 LE) + packed(1) + bgcolor(1) + aspect(1) if err := binary.Write(&buf, binary.LittleEndian, width); err != nil { t.Fatalf("write width: %v", err) } if err := binary.Write(&buf, binary.LittleEndian, height); err != nil { t.Fatalf("write height: %v", err) } buf.Write([]byte{0x00, 0x00, 0x00}) // 无全局颜色表 return buf.Bytes() } func TestCheckPixelLimit_NormalPNG(t *testing.T) { img := image.NewRGBA(image.Rect(0, 0, 100, 100)) for x := 0; x < 100; x++ { for y := 0; y < 100; y++ { img.Set(x, y, color.RGBA{R: 255, A: 255}) } } var buf bytes.Buffer if err := png.Encode(&buf, img); err != nil { t.Fatalf("encode png: %v", err) } if err := CheckPixelLimit(bytes.NewReader(buf.Bytes())); err != nil { t.Fatalf("normal image should pass, got %v", err) } } func TestCheckPixelLimit_AtLimit(t *testing.T) { // 恰好等于上限 4096×4096,应通过(校验为严格大于才拒绝)。 data := buildGIFHeader(t, 4096, 4096) if err := CheckPixelLimit(bytes.NewReader(data)); err != nil { t.Fatalf("4096×4096 should pass, got %v", err) } } func TestCheckPixelLimit_TooLarge(t *testing.T) { // 4096×4097 超过上限,应拒绝并返回统一错误。 data := buildGIFHeader(t, 4096, 4097) err := CheckPixelLimit(bytes.NewReader(data)) if err == nil { t.Fatal("oversized image should be rejected, got nil") } if !errors.Is(err, ErrImageTooLarge) { t.Fatalf("expected ErrImageTooLarge, got %v", err) } } func TestCheckPixelLimit_InvalidData(t *testing.T) { err := CheckPixelLimit(bytes.NewReader([]byte("this is not an image"))) if err == nil { t.Fatal("invalid data should error, got nil") } if errors.Is(err, ErrImageTooLarge) { t.Fatal("invalid data should not be classified as ErrImageTooLarge") } } // encodePNG 生成指定宽高的纯色 PNG,供比例校验测试使用。 func encodePNG(t *testing.T, width, height int) []byte { t.Helper() img := image.NewRGBA(image.Rect(0, 0, width, height)) var buf bytes.Buffer if err := png.Encode(&buf, img); err != nil { t.Fatalf("encode png: %v", err) } return buf.Bytes() } func TestCheckAspectRatio_Pass(t *testing.T) { // 1920×1080 精确 16:9 if err := CheckAspectRatio(bytes.NewReader(encodePNG(t, 1920, 1080)), 16, 9, DefaultAspectTolerance); err != nil { t.Fatalf("1920×1080 should pass 16:9, got %v", err) } // 1:1 正方形 if err := CheckAspectRatio(bytes.NewReader(encodePNG(t, 512, 512)), 1, 1, DefaultAspectTolerance); err != nil { t.Fatalf("512×512 should pass 1:1, got %v", err) } // 容差内的微小偏差(1281×720 ≈ 1.779,仍属 16:9) if err := CheckAspectRatio(bytes.NewReader(encodePNG(t, 1281, 720)), 16, 9, DefaultAspectTolerance); err != nil { t.Fatalf("1281×720 should pass within tolerance, got %v", err) } } func TestCheckAspectRatio_Reject(t *testing.T) { // 4:3 直传背景图应被拒绝 err := CheckAspectRatio(bytes.NewReader(encodePNG(t, 800, 600)), 16, 9, DefaultAspectTolerance) if err == nil { t.Fatal("800×600 should be rejected as 16:9") } if !errors.Is(err, ErrBadAspectRatio) { t.Fatalf("expected ErrBadAspectRatio, got %v", err) } // 竖长条不能当头像 if err := CheckAspectRatio(bytes.NewReader(encodePNG(t, 300, 500)), 1, 1, DefaultAspectTolerance); err == nil { t.Fatal("300×500 should be rejected as 1:1") } } func TestCheckAspectRatio_InvalidData(t *testing.T) { // 无法解码的数据不应误判为比例不符 err := CheckAspectRatio(bytes.NewReader([]byte("this is not an image")), 1, 1, DefaultAspectTolerance) if err == nil { t.Fatal("invalid data should error, got nil") } if errors.Is(err, ErrBadAspectRatio) { t.Fatal("invalid data should not be classified as ErrBadAspectRatio") } }