1package favicon
2
3import (
4 "bytes"
5 "context"
6 "errors"
7 "image"
8 "image/color"
9 "image/png"
10 "os"
11 "testing"
12
13 appport "github.com/bnema/dumber/internal/application/port"
14)
15
16func TestConverterWebPConvertsAlphaFixture(t *testing.T) {
17 data, err := os.ReadFile("testdata/alpha.webp")
18 if err != nil {
19 t.Fatal(err)
20 }
21 conv := NewImageConverter()
22 out, err := conv.Convert(context.Background(), data, "image/webp", []int{32})
23 if err != nil {
24 t.Fatal(err)
25 }
26 img, err := png.Decode(bytes.NewReader(out.PNG))
27 if err != nil {
28 t.Fatal(err)
29 }
30 if got := color.NRGBAModel.Convert(img.At(0, 0)).(color.NRGBA); got.A != 64 || got.R != 255 {
31 t.Fatalf("alpha pixel = %#v, want red alpha 64", got)
32 }
33 sized, err := png.Decode(bytes.NewReader(out.SizedPNG[32]))
34 if err != nil {
35 t.Fatal(err)
36 }
37 if sized.Bounds() != image.Rect(0, 0, 32, 32) {
38 t.Fatalf("sized WebP bounds = %v, want 32x32", sized.Bounds())
39 }
40}
41
42func TestConverterWebPFailuresAreFaviconMisses(t *testing.T) {
43 for _, tc := range []struct {
44 name string
45 data []byte
46 dec webpRuntime
47 }{
48 {name: "truncated", data: []byte("RIFF\x04\x00\x00\x00WEBPVP8 "), dec: &fakeWebPRuntime{infoErr: errors.New("truncated")}},
49 {name: "unavailable", data: []byte("webp"), dec: &fakeWebPRuntime{infoErr: errors.New("libwebp unavailable")}},
50 } {
51 t.Run(tc.name, func(t *testing.T) {
52 conv := newImageConverterWithWebPRuntime(tc.dec)
53 if _, err := conv.Convert(context.Background(), tc.data, "image/webp", nil); !errors.Is(err, appport.ErrFaviconMiss) {
54 t.Fatalf("err = %v, want favicon miss", err)
55 }
56 })
57 }
58}
59
60func TestWebPDecoderRejectsInputAndDecodedLimitsBeforeAllocation(t *testing.T) {
61 for _, tc := range []struct {
62 name string
63 data []byte
64 width, height int
65 }{
66 {name: "input", data: bytes.Repeat([]byte("x"), maxWebPInputBytes+1), width: 1, height: 1},
67 {name: "dimension", data: []byte("webp"), width: maxWebPDimension + 1, height: 1},
68 {name: "pixels", data: []byte("webp"), width: maxWebPDimension, height: maxWebPDimension},
69 } {
70 t.Run(tc.name, func(t *testing.T) {
71 runtime := &fakeWebPRuntime{width: tc.width, height: tc.height}
72 _, err := newWebPDecoder(runtime).Decode(tc.data)
73 if err == nil {
74 t.Fatal("Decode succeeded")
75 }
76 if runtime.decodeCalls != 0 {
77 t.Fatalf("decode calls = %d, want no allocation/decode", runtime.decodeCalls)
78 }
79 })
80 }
81}
82
83type fakeWebPRuntime struct {
84 width, height int
85 infoErr error
86 decodeCalls int
87}
88
89func (r *fakeWebPRuntime) Info(_ []byte) (int, int, error) { return r.width, r.height, r.infoErr }
90func (r *fakeWebPRuntime) DecodeRGBAInto(_, pix []byte, _, _, _ int) error {
91 r.decodeCalls++
92 for i := range pix {
93 pix[i] = 0xff
94 }
95 return nil
96}