package api
import (
"strings"
"testing"
"craftdesign.group/quarry/internal/ops"
"craftdesign.group/quarry/internal/oltp"
)
// The type comes from the BYTES, never from what the upload claimed.
// This image is served from the forge's own origin to every reader of
// every page it appears on, so the uploader's word for it is not
// evidence.
func TestTheTypeIsReadFromTheBytes(t *testing.T) {
for _, c := range []struct {
name string
raw string
want string
}{
{"png", "\x89PNG\r\n\x1a\nrest", "image/png"},
{"jpeg", "\xff\xd8\xff\xe0rest", "image/jpeg"},
{"gif87", "GIF87arest", "image/gif"},
{"gif89", "GIF89arest", "image/gif"},
{"webp", "RIFF\x00\x00\x00\x00WEBPVP8 ", "image/webp"},
} {
got, err := sniffAvatar([]byte(c.raw))
if err != nil || got != c.want {
t.Errorf("FAIL: %s sniffed as %q (err=%v), want %q", c.name, got, err, c.want)
}
}
}
// SVG is a document: it can carry script and fetch from other hosts,
// and it would be served from the forge's own origin. Refused, and
// the refusal is not an accident of the magic table.
func TestAnSVGIsNotAnImageHere(t *testing.T) {
for _, raw := range []string{
`<svg xmlns="http://www.w3.org/2000/svg"><script>alert(1)</script></svg>`,
"<?xml version=\"1.0\"?><svg/>",
} {
if got, err := sniffAvatar([]byte(raw)); err == nil {
t.Errorf("FAIL: an SVG was accepted as %q", got)
}
}
}
// RIFF alone is a container; only the WEBP form of it is an image a
// browser draws. A RIFF that is something else (a WAV, say) must not
// be stored as an image/webp the forge then serves.
func TestARIFFThatIsNotWebPIsRefused(t *testing.T) {
wav := "RIFF\x00\x00\x00\x00WAVEfmt "
if got, err := sniffAvatar([]byte(wav)); err == nil {
t.Errorf("FAIL: a WAV was accepted as %q", got)
}
}
// Anything unrecognised is refused rather than stored as some default.
func TestUnrecognisedBytesAreRefused(t *testing.T) {
for _, raw := range []string{"", "hello", "\x00\x01\x02\x03"} {
if _, err := sniffAvatar([]byte(raw)); err == nil {
t.Errorf("FAIL: %q was accepted", raw)
}
}
}
// The digest is in the key, so replacing a mark writes a NEW object
// rather than overwriting one a reader may be mid-fetch of — and a
// cache can never hold the wrong image for a key it already has.
func TestTheKeyCarriesTheDigest(t *testing.T) {
a := avatarKey(oltp.AvatarRepo, "quarry", strings.Repeat("a", 64))
b := avatarKey(oltp.AvatarRepo, "quarry", strings.Repeat("b", 64))
if a == b {
t.Errorf("FAIL: two different images share a key: %s", a)
}
if !strings.HasPrefix(a, "avatars/repo/quarry/") {
t.Errorf("FAIL: key = %q", a)
}
// A team and a repository of one name do not collide.
if avatarKey(oltp.AvatarTeam, "quarry", strings.Repeat("a", 64)) == a {
t.Errorf("FAIL: a team and a repository share a key")
}
}
// The kinds this op accepts are the kinds that exist. The separator in
// a oneof is a PIPE: written with a space it is one alternative — the
// literal "repo team" — so every upload was refused with "field
// \"kind\" must be one of repo team", which reads like a list and is
// not one.
func TestBothKindsAreActuallyAccepted(t *testing.T) {
for _, in := range []avatarSetIn{
{Kind: "repo", Name: "quarry", Body: "x"},
{Kind: "team", Name: "grove", Body: "x"},
} {
if err := ops.Validate(in); err != nil {
t.Errorf("FAIL: kind %q refused: %v", in.Kind, err)
}
}
// And a kind nobody defined is still refused.
bad := avatarSetIn{Kind: "person", Name: "dmestas", Body: "x"}
if err := ops.Validate(bad); err == nil {
t.Error("FAIL: an unknown kind was accepted")
}
}