Go · 3197 bytes Raw Blame History
1 // SPDX-License-Identifier: AGPL-3.0-or-later
2
3 package avatars_test
4
5 import (
6 "bytes"
7 "image"
8 "image/color"
9 "image/jpeg"
10 "image/png"
11 "strings"
12 "testing"
13
14 "github.com/tenseleyFlow/shithub/internal/avatars"
15 )
16
17 func makePNG(t *testing.T, w, h int) []byte {
18 t.Helper()
19 img := image.NewRGBA(image.Rect(0, 0, w, h))
20 for y := 0; y < h; y++ {
21 for x := 0; x < w; x++ {
22 img.Set(x, y, color.RGBA{R: 200, G: 100, B: 50, A: 255})
23 }
24 }
25 buf := &bytes.Buffer{}
26 if err := png.Encode(buf, img); err != nil {
27 t.Fatalf("encode: %v", err)
28 }
29 return buf.Bytes()
30 }
31
32 func makeJPEG(t *testing.T, w, h int) []byte {
33 t.Helper()
34 img := image.NewRGBA(image.Rect(0, 0, w, h))
35 buf := &bytes.Buffer{}
36 if err := jpeg.Encode(buf, img, &jpeg.Options{Quality: 80}); err != nil {
37 t.Fatalf("encode: %v", err)
38 }
39 return buf.Bytes()
40 }
41
42 func TestProcess_PNGRoundtrip(t *testing.T) {
43 t.Parallel()
44 src := makePNG(t, 800, 600) // landscape; expect center-crop to 600×600.
45 variants, hash, err := avatars.Process(bytes.NewReader(src))
46 if err != nil {
47 t.Fatalf("process: %v", err)
48 }
49 if len(variants) != len(avatars.VariantSizes) {
50 t.Fatalf("variants = %d, want %d", len(variants), len(avatars.VariantSizes))
51 }
52 for i, want := range avatars.VariantSizes {
53 v := variants[i]
54 if v.Size != want {
55 t.Errorf("variant[%d].Size = %d, want %d", i, v.Size, want)
56 }
57 cfg, err := png.DecodeConfig(bytes.NewReader(v.Data))
58 if err != nil {
59 t.Errorf("variant[%d] decode: %v", i, err)
60 continue
61 }
62 if cfg.Width != want || cfg.Height != want {
63 t.Errorf("variant[%d] dims = %dx%d, want %dx%d", i, cfg.Width, cfg.Height, want, want)
64 }
65 }
66 if len(hash) == 0 {
67 t.Fatal("hash is empty")
68 }
69 }
70
71 func TestProcess_JPEGAccepted(t *testing.T) {
72 t.Parallel()
73 src := makeJPEG(t, 1000, 1000)
74 if _, _, err := avatars.Process(bytes.NewReader(src)); err != nil {
75 t.Fatalf("jpeg accepted: %v", err)
76 }
77 }
78
79 func TestProcess_RejectsUnsupportedFormat(t *testing.T) {
80 t.Parallel()
81 _, _, err := avatars.Process(strings.NewReader("not an image at all"))
82 if err == nil {
83 t.Fatal("expected error for non-image input")
84 }
85 }
86
87 func TestProcess_RejectsTooLarge(t *testing.T) {
88 t.Parallel()
89 // Build a payload that exceeds MaxUploadBytes.
90 big := make([]byte, avatars.MaxUploadBytes+10)
91 _, _, err := avatars.Process(bytes.NewReader(big))
92 if err != avatars.ErrTooLarge {
93 t.Fatalf("err = %v, want ErrTooLarge", err)
94 }
95 }
96
97 func TestProcess_RejectsDecompressionBomb(t *testing.T) {
98 t.Parallel()
99 // Construct a tiny PNG header that *claims* huge dimensions but
100 // stays well under MaxUploadBytes.
101 // 10000 × 10000 = 100M pixels > 24M cap.
102 // Build a real but huge-size PNG using NewPaletted to keep file size low.
103 pal := []color.Color{color.White, color.Black}
104 img := image.NewPaletted(image.Rect(0, 0, 8000, 8000), pal)
105 buf := &bytes.Buffer{}
106 if err := png.Encode(buf, img); err != nil {
107 t.Fatalf("encode: %v", err)
108 }
109 if buf.Len() > avatars.MaxUploadBytes {
110 t.Skipf("constructed PNG (%d bytes) exceeds MaxUploadBytes; skipping", buf.Len())
111 }
112 _, _, err := avatars.Process(bytes.NewReader(buf.Bytes()))
113 if err != avatars.ErrDecompression {
114 t.Fatalf("err = %v, want ErrDecompression", err)
115 }
116 }
117