package api
import (
"context"
"errors"
"strings"
"testing"
"craftdesign.group/quarry/internal/index"
"craftdesign.group/quarry/internal/ops"
"craftdesign.group/quarry/internal/sniff"
)
// The card's four shapes come from the BYTES, and the op is the one
// place that decides (RFC 0016 §Inline). Each case is a signature a
// card draws differently.
func TestAttachmentHeadKinds(t *testing.T) {
for _, c := range []struct {
what string
raw []byte
kind string
mime string
}{
{"png", append([]byte("\x89PNG\r\n\x1a\n"), []byte("IHDR")...), sniff.KindImage, "image/png"},
{"webp", append([]byte("RIFF....WEBPVP8 "), []byte("rest")...), sniff.KindImage, "image/webp"},
{"mp4", append([]byte{0, 0, 0, 0x18}, []byte("ftypisom....")...), sniff.KindMedia, "video/mp4"},
{"wav", append([]byte("RIFF....WAVEfmt "), []byte("rest")...), sniff.KindMedia, "audio/wav"},
{"text", []byte("one\ntwo\n"), sniff.KindText, ""},
{"binary", []byte("%PDF-1.7\n\x00\x01"), sniff.KindOther, ""},
} {
got := attachmentHead(c.raw, len(c.raw), false)
if got.Kind != c.kind || got.Mimetype != c.mime {
t.Errorf("FAIL: %s answered kind %q type %q, want %q %q",
c.what, got.Kind, got.Mimetype, c.kind, c.mime)
}
if got.Size != len(c.raw) {
t.Errorf("FAIL: %s size %d, want %d", c.what, got.Size, len(c.raw))
}
if got.Truncated {
t.Errorf("FAIL: %s was reported truncated", c.what)
}
}
t.Log("ok: attachment.head classifies image, media, text and other from the bytes")
}
// Only text carries a preview, and it stops at the op's own window.
func TestAttachmentHeadTextPreview(t *testing.T) {
var b strings.Builder
for i := 0; i < 40; i++ {
b.WriteString("line\n")
}
raw := []byte(b.String())
got := attachmentHead(raw, len(raw), false)
if got.Lines != 40 {
t.Errorf("FAIL: total lines %d, want 40", got.Lines)
}
if n := strings.Count(got.Text, "\n") + 1; n != maxHeadLines {
t.Errorf("FAIL: preview holds %d lines, want %d", n, maxHeadLines)
}
// The window a caller asks for narrows the answer; it never widens
// past what was remembered.
narrow := headOut("notes.txt", got, 3)
if n := strings.Count(narrow.Text, "\n") + 1; n != 3 {
t.Errorf("FAIL: a 3-line request answered %d lines", n)
}
if wide := headOut("notes.txt", got, 1000); strings.Count(wide.Text, "\n")+1 != maxHeadLines {
t.Errorf("FAIL: the line cap did not hold")
}
if bin := attachmentHead([]byte("\x00\x01\x02"), 3, false); bin.Text != "" || bin.Lines != 0 {
t.Errorf("FAIL: binary carried a preview: %+v", bin)
}
t.Log("ok: only text previews, capped at the op's own window")
}
// The read is BOUNDED, so a big file's verdict comes from its front. A
// line count taken from a prefix is a floor, and the answer says so
// rather than claiming a total it never saw.
func TestAttachmentHeadOnATruncatedPrefix(t *testing.T) {
whole := 5 << 20
prefix := []byte(strings.Repeat("line\n", 10) + "half-a-li")
got := attachmentHead(prefix, whole, true)
if got.Kind != sniff.KindText {
t.Fatalf("FAIL: kind %q", got.Kind)
}
if got.Size != whole {
t.Fatalf("FAIL: size %d, want the WHOLE file's %d", got.Size, whole)
}
if !got.Truncated {
t.Fatal("FAIL: a cut prefix was not reported truncated")
}
// The half line at the cut is neither counted nor previewed.
if got.Lines != 10 {
t.Errorf("FAIL: lines %d, want the 10 whole ones", got.Lines)
}
if strings.Contains(got.Text, "half-a-li") {
t.Errorf("FAIL: the preview shows a line cut in half: %q", got.Text)
}
t.Log("ok: a truncated prefix reports the whole size, the lines it saw, and says it was cut")
}
// A UTF-8 document cut mid-character is still a UTF-8 document. Without
// the trim, every long text file whose prefix ends inside a rune would
// be drawn as a binary blob at random.
func TestAttachmentHeadSurvivesARuneCutByTheBound(t *testing.T) {
body := []byte("héllo wörld\nsecond line\n")
cut := body[:len(body)-1] // lands inside nothing; force a real cut below
multi := append([]byte("first line\n"), []byte("caf\xc3")...)
if got := attachmentHead(multi, 9999, true); got.Kind != sniff.KindText {
t.Fatalf("FAIL: a prefix ending mid-rune read as %q", got.Kind)
}
if got := attachmentHead(cut, len(body), true); got.Kind != sniff.KindText {
t.Fatalf("FAIL: kind %q", got.Kind)
}
t.Log("ok: a rune the bound cut in half does not turn a text file binary")
}
// The op is gated by the A card like every other attachment route: a
// hash the index does not know as a live attachment is not found, and
// no byte is read for it.
func TestAttachmentHeadGatesOnTheACard(t *testing.T) {
_, err := attachRegistry(t).Invoke(context.Background(), "attachment.head",
attachmentHeadIn{Repo: "test", Hash: "nosuchblob"})
if !errors.Is(err, ops.ErrNotFound) {
t.Fatalf("FAIL: unknown hash answered %v, want ErrNotFound", err)
}
t.Log("ok: attachment.head refuses a hash that is not a live attachment")
}
// A remembered verdict is served without touching the store. The proof
// is that it works at all here: these test repos carry no Zit client,
// so a miss would panic on the read the memo exists to avoid.
func TestAttachmentHeadServesTheMemoWithoutTheStore(t *testing.T) {
ix := attachedIndex(t)
if err := ix.PutAttachmentHead("blobw", index.AttachmentHead{
Size: 41 * 1024, Kind: sniff.KindImage, Mimetype: "image/png",
}); err != nil {
t.Fatal(err)
}
r := ops.NewRegistry()
if err := RegisterAll(r, Deps{Version: "v", Repos: testRepos(ix)}); err != nil {
t.Fatal(err)
}
got, err := r.Invoke(context.Background(), "attachment.head",
attachmentHeadIn{Repo: "test", Hash: "blobw"})
if err != nil {
t.Fatalf("FAIL: %v", err)
}
out := got.(attachmentHeadOut)
if out.Kind != sniff.KindImage || out.Size != 41*1024 || out.Name != "crash.png" {
t.Fatalf("FAIL: memo answer = %+v", out)
}
t.Log("ok: a remembered verdict is answered from the index alone")
}