go-loose/internal/avatar/avatar_test.go

173 lines
4.9 KiB
Go
Raw Normal View History

2026-10-04 12:24:13 +02:00
package avatar
import (
"bytes"
"encoding/base64"
"errors"
"image"
"image/color"
"image/gif"
"image/jpeg"
"image/png"
"testing"
)
func samplePNG(t *testing.T, width, height int) []byte {
t.Helper()
canvas := image.NewRGBA(image.Rect(0, 0, width, height))
canvas.Set(0, 0, color.RGBA{R: 125, G: 207, B: 255, A: 255})
buffer := &bytes.Buffer{}
if err := png.Encode(buffer, canvas); err != nil {
t.Fatalf("encode png: %v", err)
}
return buffer.Bytes()
}
func encode(t *testing.T, format string, width, height int) []byte {
t.Helper()
canvas := image.NewRGBA(image.Rect(0, 0, width, height))
buffer := &bytes.Buffer{}
var err error
switch format {
case "jpeg":
err = jpeg.Encode(buffer, canvas, nil)
case "gif":
err = gif.Encode(buffer, canvas, nil)
default:
err = png.Encode(buffer, canvas)
}
if err != nil {
t.Fatalf("encode %s: %v", format, err)
}
return buffer.Bytes()
}
func TestParseAcceptsSupportedFormats(t *testing.T) {
for _, format := range []string{"png", "jpeg", "gif"} {
t.Run(format, func(t *testing.T) {
raw := encode(t, format, 8, 8)
data, contentType, err := Parse(base64.StdEncoding.EncodeToString(raw))
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if !bytes.Equal(data, raw) {
t.Fatal("Parse() did not return the uploaded bytes")
}
if contentType != "image/"+map[string]string{"png": "png", "jpeg": "jpeg", "gif": "gif"}[format] {
t.Fatalf("content type = %q", contentType)
}
})
}
}
func TestParseAcceptsDataURL(t *testing.T) {
raw := samplePNG(t, 4, 4)
dataURL := "data:image/png;base64," + base64.StdEncoding.EncodeToString(raw)
data, contentType, err := Parse(dataURL)
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if len(data) != len(raw) || contentType != "image/png" {
t.Fatalf("Parse() = %d bytes of %q", len(data), contentType)
}
}
func TestParseRejectsMismatchedDataURLMediaType(t *testing.T) {
raw := samplePNG(t, 4, 4)
dataURL := "data:image/gif;base64," + base64.StdEncoding.EncodeToString(raw)
if _, _, err := Parse(dataURL); !errors.Is(err, ErrUnsupported) {
t.Fatalf("error = %v, want %v", err, ErrUnsupported)
}
}
func TestParseRejectsNonBase64DataURL(t *testing.T) {
raw := samplePNG(t, 4, 4)
dataURL := "data:image/png," + base64.StdEncoding.EncodeToString(raw)
if _, _, err := Parse(dataURL); !errors.Is(err, ErrEmpty) {
t.Fatalf("error = %v, want %v", err, ErrEmpty)
}
}
func TestParseRejectsMalformedAndUnsupportedInput(t *testing.T) {
tests := []struct {
name string
value string
want error
}{
{name: "empty", value: " ", want: ErrEmpty},
{name: "not base64", value: "!!!not base64!!!", want: ErrEncoding},
{name: "plain text", value: base64.StdEncoding.EncodeToString([]byte("hello world, not an image")), want: ErrUnsupported},
{name: "svg", value: base64.StdEncoding.EncodeToString([]byte(`<svg xmlns="http://www.w3.org/2000/svg"/>`)), want: ErrUnsupported},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if _, _, err := Parse(test.value); !errors.Is(err, test.want) {
t.Fatalf("error = %v, want %v", err, test.want)
}
})
}
}
func TestParseRejectsOversizedUploads(t *testing.T) {
huge := base64.StdEncoding.EncodeToString(make([]byte, MaxBytes+1))
if _, _, err := Parse(huge); !errors.Is(err, ErrTooLarge) {
t.Fatalf("error = %v, want %v", err, ErrTooLarge)
}
}
func TestInspectRejectsDecompressionBombs(t *testing.T) {
oversized := samplePNG(t, 1, MaxDimension+1)
if _, err := Inspect(oversized); !errors.Is(err, ErrDimensions) {
t.Fatalf("error = %v, want %v", err, ErrDimensions)
}
raw := samplePNG(t, MaxDimension, MaxDimension)
if _, err := Inspect(raw); err != nil {
t.Fatalf("Inspect() rejected the maximum size: %v", err)
}
}
func TestInspectRejectsEmptyBytes(t *testing.T) {
if _, err := Inspect(nil); !errors.Is(err, ErrEmpty) {
t.Fatalf("error = %v, want %v", err, ErrEmpty)
}
}
func TestNewKeyIsUnguessableAndValid(t *testing.T) {
seen := make(map[string]bool)
for range 100 {
key, err := NewKey()
if err != nil {
t.Fatalf("NewKey() error = %v", err)
}
if !ValidKey(key) {
t.Fatalf("NewKey() produced an invalid key %q", key)
}
if seen[key] {
t.Fatal("NewKey() repeated a key")
}
seen[key] = true
}
}
func TestValidKeyRejectsMalformedLocators(t *testing.T) {
tests := []string{"", "short", "../etc/passwd", string(bytes.Repeat([]byte("a"), 44)), "a/b+c="}
for _, test := range tests {
if ValidKey(test) {
t.Fatalf("ValidKey(%q) = true", test)
}
}
}
func TestETagIsStableAndContentDerived(t *testing.T) {
first, second := samplePNG(t, 4, 4), samplePNG(t, 5, 5)
if ETag(first) != ETag(first) {
t.Fatal("ETag is not stable for identical content")
}
if ETag(first) == ETag(second) {
t.Fatal("ETag must change when the content changes")
}
if tag := ETag(first); len(tag) < 3 || tag[0] != '"' || tag[len(tag)-1] != '"' {
t.Fatalf("ETag = %q, want a quoted validator", tag)
}
}