fluid-telegram/internal/avatar/avatar_test.go

135 lines
3.9 KiB
Go
Raw Normal View History

package avatar
import (
"bytes"
"encoding/binary"
"errors"
"os"
"path/filepath"
"testing"
)
func png(t *testing.T, w, h uint32, pad int) []byte {
t.Helper()
var b bytes.Buffer
b.WriteString("\x89PNG\r\n\x1a\n")
b.Write([]byte{0, 0, 0, 13})
b.WriteString("IHDR")
binary.Write(&b, binary.BigEndian, w)
binary.Write(&b, binary.BigEndian, h)
b.Write(make([]byte, pad))
return b.Bytes()
}
// newRepo makes a temp directory that is unambiguously a repository root.
// Without the marker, resolution walks up into whatever repository happens to
// contain the temp directory -- this machine has a /tmp/.git, which is exactly
// the ambient surprise these tests must not depend on.
func newRepo(t *testing.T) string {
t.Helper()
dir := t.TempDir()
write(t, dir, ".git/HEAD", []byte("ref: refs/heads/main\n"))
return dir
}
func write(t *testing.T, dir, name string, data []byte) string {
t.Helper()
p := filepath.Join(dir, name)
os.MkdirAll(filepath.Dir(p), 0o755)
if err := os.WriteFile(p, data, 0o644); err != nil {
t.Fatal(err)
}
return p
}
// The schema documents the avatar path as repo-relative. Resolving it against
// the spec's own directory instead makes a correct spec fail, which is exactly
// what happened with the real campaign spec.
func TestPathIsRepoRelativeNotSpecRelative(t *testing.T) {
repo := newRepo(t)
write(t, repo, "assets/logo.png", png(t, 600, 600, 64))
specPath := write(t, repo, "presence/telegram.yaml", []byte("presence: {}\n"))
img, err := Load(specPath, "assets/logo.png")
if err != nil {
t.Fatalf("repo-relative path did not resolve: %v", err)
}
if img.Width != 600 || !img.Square() {
t.Errorf("got %dx%d", img.Width, img.Height)
}
}
func TestMissingIsDistinguishable(t *testing.T) {
repo := newRepo(t)
spec := write(t, repo, "spec.yaml", []byte("x"))
_, err := Load(spec, "nope.png")
if !errors.Is(err, ErrMissing) {
t.Fatalf("a missing file must be distinguishable from a bad one: %v", err)
}
}
func TestRejectsBadImages(t *testing.T) {
repo := newRepo(t)
spec := write(t, repo, "spec.yaml", []byte("x"))
write(t, repo, "small.png", png(t, 128, 128, 16))
if _, err := Load(spec, "small.png"); err == nil {
t.Error("accepted an image below the minimum dimension")
} else if errors.Is(err, ErrMissing) {
t.Error("a too-small image is not a missing one")
}
write(t, repo, "notpng.png", []byte("GIF89a and then some padding bytes here"))
if _, err := Load(spec, "notpng.png"); err == nil {
t.Error("accepted a non-PNG")
}
write(t, repo, "zero.png", png(t, 0, 600, 16))
if _, err := Load(spec, "zero.png"); err == nil {
t.Error("accepted a zero dimension")
}
}
// The digest is what decides whether a picture is re-sent, so identical bytes
// must give an identical digest and different bytes must not.
func TestDigestIsContentAddressed(t *testing.T) {
repo := newRepo(t)
spec := write(t, repo, "spec.yaml", []byte("x"))
write(t, repo, "a.png", png(t, 600, 600, 32))
write(t, repo, "b.png", png(t, 600, 600, 32))
write(t, repo, "c.png", png(t, 600, 600, 33))
a, _ := Load(spec, "a.png")
b, _ := Load(spec, "b.png")
c, _ := Load(spec, "c.png")
if a.Digest != b.Digest {
t.Error("identical content gave different digests")
}
if a.Digest == c.Digest {
t.Error("different content gave the same digest")
}
}
// A non-square image is accepted but must say what a viewer will lose.
func TestCropNote(t *testing.T) {
repo := newRepo(t)
spec := write(t, repo, "spec.yaml", []byte("x"))
write(t, repo, "wide.png", png(t, 907, 885, 32))
img, err := Load(spec, "wide.png")
if err != nil {
t.Fatal(err)
}
if img.Square() {
t.Fatal("907x885 is not square")
}
if note := img.CropNote(); note == "" {
t.Error("a non-square image should explain the crop")
}
write(t, repo, "sq.png", png(t, 900, 900, 32))
sq, _ := Load(spec, "sq.png")
if sq.CropNote() != "" {
t.Errorf("a square image needs no note, got %q", sq.CropNote())
}
}