Artifact 31491bcac71ecded7008f95781ab2e0fac93c201ceb56d5e1e759f22f6c1be94:
- File
internal/index/attachment_test.go
— part of check-in
[4831e9bef1]
at
2026-08-16 18:34:05
on branch trunk
— index: fold attachments (A card)
The last data delta against fossil. quarry parsed the A card into an Attach and dropped it, so attachments were invisible: sqlite showed none of its 11, tcl none of its 3,333. Both now match fossil's own table exactly.
Fossil models attachments as a log, not a set — the newest card per filename wins and a card with no source is a DELETION — so resolving them wrong resurrects deleted files. Real corpora exercise that: 1 deleted on sqlite, 10 on tcl.
Content is not linkable yet. Serving arbitrary attached bytes needs a decided Content-Disposition and CSP posture, so the page names each file and the raw-artifact route stays its own decision. (user: dmestas, size: 2524) [annotate] [blame] [check-ins using]
package index import ( "path/filepath" "testing" "craftdesign.group/quarry/internal/artifact" ) func openIndex(t *testing.T) *Index { t.Helper() ix, err := Open(filepath.Join(t.TempDir(), "index.db")) if err != nil { t.Fatal(err) } t.Cleanup(func() { ix.Close() }) return ix } func attach(hash, name, target, src, date string) Parsed { return Parsed{Hash: hash, Art: &artifact.Artifact{ Type: artifact.Attachment, Date: date, User: "dan", Attachment: &artifact.Attach{Name: name, Target: target, Source: src}, }} } // Fossil models attachments as a log, not a set: the latest A card per // filename wins, and one with no source is a deletion. Getting that // wrong resurrects deleted files. func TestAttachmentsResolveLikeFossil(t *testing.T) { ix := openIndex(t) tkt := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" if err := ix.FoldNow([]Parsed{ attach("h1", "spec.pdf", tkt, "src1", "2026-01-01T00:00:00"), attach("h2", "spec.pdf", tkt, "src2", "2026-01-02T00:00:00"), // replaced attach("h3", "notes.txt", tkt, "src3", "2026-01-03T00:00:00"), attach("h4", "notes.txt", tkt, "", "2026-01-04T00:00:00"), // deleted attach("h5", "other.txt", "different-target", "src5", "2026-01-05T00:00:00"), }); err != nil { t.Fatal(err) } got, err := ix.AttachmentsFor(tkt) if err != nil { t.Fatal(err) } if len(got) != 1 { t.Fatalf("live attachments = %+v, want just the replaced spec.pdf", got) } if got[0].Name != "spec.pdf" || got[0].Src != "src2" { t.Fatalf("wrong revision won: %+v", got[0]) } // A deletion followed by a re-attach brings the file back. if err := ix.FoldNow([]Parsed{ attach("h6", "notes.txt", tkt, "src6", "2026-01-06T00:00:00"), }); err != nil { t.Fatal(err) } got, _ = ix.AttachmentsFor(tkt) if len(got) != 2 { t.Fatalf("re-attach did not restore the file: %+v", got) } // Every card is still counted — the census matches fossil's // attachment table, deletions included. n, err := ix.CountAttachments() if err != nil { t.Fatal(err) } if n != 6 { t.Fatalf("counted %d attachment artifacts, want 6", n) } } // Folds are idempotent: redelivery must not double-count. func TestAttachmentFoldIsIdempotent(t *testing.T) { ix := openIndex(t) batch := []Parsed{attach("h1", "a.txt", "tgt", "src", "2026-01-01T00:00:00")} if err := ix.FoldNow(batch); err != nil { t.Fatal(err) } if err := ix.FoldNow(batch); err != nil { t.Fatal(err) } n, _ := ix.CountAttachments() if n != 1 { t.Fatalf("redelivery produced %d rows", n) } }