package imagenorm import ( "bytes" "image" "image/jpeg" "image/png" "os" "testing" ) // pngBytes and jpegBytes generate in-memory fixtures for the two formats Go can // encode; heic/webp come from testdata (Go has no encoder for them). func pngBytes(t *testing.T, w, h int) []byte { t.Helper() m := image.NewRGBA(image.Rect(0, 0, w, h)) for y := range h { for x := range w { m.Pix[m.PixOffset(x, y)+0] = uint8(x) m.Pix[m.PixOffset(x, y)+3] = 255 } } var b bytes.Buffer if err := png.Encode(&b, m); err != nil { t.Fatalf("encode png: %v", err) } return b.Bytes() } func jpegBytes(t *testing.T, w, h int) []byte { t.Helper() m := image.NewRGBA(image.Rect(0, 0, w, h)) var b bytes.Buffer if err := jpeg.Encode(&b, m, nil); err != nil { t.Fatalf("encode jpeg: %v", err) } return b.Bytes() } func readTestdata(t *testing.T, name string) []byte { t.Helper() b, err := os.ReadFile("testdata/" + name) if err != nil { t.Fatalf("read %s: %v", name, err) } return b } // TestNormalizeAllFormats is the load-bearing test: every format pansy claims to // accept must round-trip to a valid JPEG. It exists specifically to catch a // dropped blank import — the failure mode where the common format (PNG) breaks // while the exotic one (HEIC) works, because someone deleted `_ "image/png"`. func TestNormalizeAllFormats(t *testing.T) { cases := []struct { name string input []byte wantFormat string }{ {"png", pngBytes(t, 120, 90), "png"}, {"jpeg", jpegBytes(t, 120, 90), "jpeg"}, {"heic", readTestdata(t, "sample.heic"), "heic"}, {"webp", readTestdata(t, "sample.webp"), "webp"}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { out, format, err := Normalize(bytes.NewReader(tc.input), Options{}) if err != nil { t.Fatalf("Normalize(%s): %v", tc.name, err) } if format != tc.wantFormat { t.Errorf("format = %q, want %q", format, tc.wantFormat) } // The output must itself be a decodable JPEG. _, outFormat, err := image.Decode(bytes.NewReader(out)) if err != nil { t.Fatalf("output isn't a valid image: %v", err) } if outFormat != "jpeg" { t.Errorf("output format = %q, want jpeg", outFormat) } }) } } // TestNormalizeDownscales checks a large image is shrunk to fit MaxDim on its // longest edge with aspect ratio preserved, and a small one is left alone. func TestNormalizeDownscales(t *testing.T) { // 4000x1000 → longest 4000, MaxDim 2048 → scaled to 2048x512. big := pngBytes(t, 4000, 1000) out, _, err := Normalize(bytes.NewReader(big), Options{}) if err != nil { t.Fatalf("Normalize: %v", err) } cfg, _, err := image.DecodeConfig(bytes.NewReader(out)) if err != nil { t.Fatalf("decode out: %v", err) } if cfg.Width != 2048 { t.Errorf("width = %d, want 2048 (longest edge clamped)", cfg.Width) } if cfg.Height != 512 { t.Errorf("height = %d, want 512 (aspect preserved)", cfg.Height) } // A small image within bounds keeps its dimensions. small := pngBytes(t, 100, 80) out, _, err = Normalize(bytes.NewReader(small), Options{}) if err != nil { t.Fatalf("Normalize small: %v", err) } cfg, _, _ = image.DecodeConfig(bytes.NewReader(out)) if cfg.Width != 100 || cfg.Height != 80 { t.Errorf("small image resized to %dx%d, want 100x80", cfg.Width, cfg.Height) } } // TestNormalizeRejectsOversizeInput: an input past the byte cap is ErrTooLarge, // refused without a full decode. func TestNormalizeRejectsOversizeInput(t *testing.T) { big := pngBytes(t, 500, 500) _, _, err := Normalize(bytes.NewReader(big), Options{MaxBytes: 100}) if err != ErrTooLarge { t.Errorf("over-cap input err = %v, want ErrTooLarge", err) } } // TestNormalizeRejectsBombDimensions: a small file claiming a huge canvas is // refused before the bitmap is allocated. A tiny MaxDim doesn't matter — the // guard is on the DECODED pixel count, checked from DecodeConfig. func TestNormalizeRejectsGarbage(t *testing.T) { _, _, err := Normalize(bytes.NewReader([]byte("not an image at all")), Options{}) if err != ErrUnsupported { t.Errorf("garbage err = %v, want ErrUnsupported", err) } // A truncated image (valid header, cut body) also fails cleanly, not a panic. png := pngBytes(t, 100, 100) _, _, err = Normalize(bytes.NewReader(png[:len(png)/2]), Options{}) if err != ErrUnsupported { t.Errorf("truncated image err = %v, want ErrUnsupported", err) } }