package api
import (
"encoding/base64"
"errors"
"strings"
"testing"
"craftdesign.group/quarry/internal/attach"
"craftdesign.group/quarry/internal/ops"
)
// A stub must never be answered as the file it stands for.
//
// This is the bug as it was measured: attachment.list gave src
// ee99187e, GET /attachment/ee99187e answered 200 with the attachment's
// REAL name, the stub's 143-byte size and the stub's bytes as content,
// and nothing in the answer distinguished it from the file. Somebody
// downloading a video got a few lines of text and no way to tell.
func TestAttachmentGetDoesNotServeTheStubAsTheFile(t *testing.T) {
stub := attach.Stub{
SHA256: "4f9a000000000000000000000000000000000000000000000000000000000e01c"[:64],
Size: 18349022,
Type: "video/mp4",
Name: "screen-recording.mp4",
}
out, err := attachmentGetAnswer("ee99187e", "screen-recording.mp4", stub.Format())
if err == nil {
t.Fatalf("FAIL: an external attachment was ANSWERED (size %d, %d bytes of content): "+
"a caller that writes what it is given writes a file that is not the file",
out.Size, len(out.Content))
}
if !errors.Is(err, ops.ErrInvalid) {
t.Errorf("FAIL: refused with %v, want an invalid-request so no client reads it as bytes", err)
}
// The refusal has to carry the three things a caller needs to
// recover: that it is external, how big the real file is, and where
// the bytes actually are.
for _, want := range []string{"external", "18349022", "/link", stub.SHA256} {
if !strings.Contains(err.Error(), want) {
t.Errorf("FAIL: the refusal does not mention %q: %v", want, err)
}
}
t.Logf("ok: refused, and said where to go (%v)", err)
}
// A real attachment is untouched: this fix must not cost the ordinary
// case, which is every attachment that arrived by import or sync.
func TestAttachmentGetStillServesARealFile(t *testing.T) {
raw := []byte("\x89PNG\r\n\x1a\nIHDR and then some bytes")
out, err := attachmentGetAnswer("abc123", "shot.png", raw)
if err != nil {
t.Fatalf("FAIL: a real file errored: %v", err)
}
if out.Size != len(raw) {
t.Errorf("FAIL: size %d, want %d", out.Size, len(raw))
}
got, derr := base64.StdEncoding.DecodeString(out.Content)
if derr != nil || string(got) != string(raw) {
t.Errorf("FAIL: content did not round-trip (%v)", derr)
}
t.Logf("ok: a real file still serves its own %d bytes", len(raw))
}
// A blob that OPENS like a stub and does not parse is not a file
// either. Falling back to serving it would put the bug back for exactly
// the bytes least likely to be what they claim.
func TestAttachmentGetRefusesAnUnreadableDeclaration(t *testing.T) {
broken := []byte(attach.Header + "\nsha256 not-a-hash\n")
_, err := attachmentGetAnswer("bad", "thing.bin", broken)
if err == nil {
t.Fatal("FAIL: an unparseable declaration was answered rather than refused")
}
if !errors.Is(err, ops.ErrNotFound) {
t.Errorf("FAIL: refused with %v, want a not-found", err)
}
t.Logf("ok: refused (%v)", err)
}