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()) } }