package api
import (
"path/filepath"
"strings"
"testing"
"craftdesign.group/quarry/internal/artifact"
"craftdesign.group/quarry/internal/index"
)
// ticketAt folds one ticket with the given status and returns its index.
func ticketAt(t *testing.T, id, status string) *index.Index {
t.Helper()
ix, err := index.Open(filepath.Join(t.TempDir(), "i.db"))
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { ix.Close() })
if err := ix.FoldNow([]index.Parsed{{Hash: id, Art: &artifact.Artifact{
Type: artifact.Ticket, TicketID: id, Date: "2026-09-01T10:00:00", User: "dan",
Fields: []artifact.Field{{Name: "title", Value: "a thing"}, {Name: "status", Value: status}},
}}}); err != nil {
t.Fatal(err)
}
return ix
}
func closeIDs(repo string, ix *index.Index, title string) []string {
var out []string
for _, t := range (Deps{}).ticketsToClose(repo, ix, title) {
out = append(out, t.ID)
}
return out
}
const openTicket = "cafb1f21be00000000000000000000000000000000000000000000000000abcd"
// The whole point: a landing that says it closes a ticket picks it.
func TestALandingPicksTheTicketItNames(t *testing.T) {
ix := ticketAt(t, openTicket, "Open")
got := closeIDs("quarry", ix, "zitcli: record every store subprocess. closes cafb1f21be")
if len(got) != 1 || got[0] != openTicket {
t.Errorf("FAIL: picked %v, want the named ticket", got)
}
}
// A ticket somebody already closed is left exactly as they left it —
// re-closing would overwrite a resolution they chose with a generic one.
func TestAnAlreadyClosedTicketIsLeftAlone(t *testing.T) {
ix := ticketAt(t, openTicket, "Closed")
if got := closeIDs("quarry", ix, "closes cafb1f21be"); len(got) != 0 {
t.Errorf("FAIL: picked %v, want nothing — it is already closed", got)
}
}
// A reference that resolves to nothing is not an error and not a close:
// a landing cannot settle something by naming it.
func TestANameThatResolvesToNothingClosesNothing(t *testing.T) {
ix := ticketAt(t, openTicket, "Open")
if got := closeIDs("quarry", ix, "closes 0123456789ab"); len(got) != 0 {
t.Errorf("FAIL: picked %v for a ticket that does not exist", got)
}
}
// Another repository's ticket is never settled from here: it resolves
// against a store this caller may not even be able to read.
func TestAnotherRepositorysTicketIsNotClosed(t *testing.T) {
ix := ticketAt(t, openTicket, "Open")
if got := closeIDs("quarry", ix, "closes other:ticket/cafb1f21be"); len(got) != 0 {
t.Errorf("FAIL: picked %v from another repository", got)
}
// Naming our OWN repository explicitly is still ours.
if got := closeIDs("quarry", ix, "closes quarry:ticket/cafb1f21be"); len(got) != 1 {
t.Errorf("FAIL: an explicit same-repository reference gave %v", got)
}
}
// Prose that mentions a ticket without governing it settles nothing.
// The cost of getting this wrong is closing somebody's open work.
func TestProseDoesNotSettleAnything(t *testing.T) {
ix := ticketAt(t, openTicket, "Open")
for _, title := range []string{
"see cafb1f21be for why",
"this closes the gap that cafb1f21be describes",
"landing-list-live: the queue follows the repository",
} {
if got := closeIDs("quarry", ix, title); len(got) != 0 {
t.Errorf("FAIL: %q settled %v", title, got)
}
}
}
// The note left on the ticket says which branch settled it, so the
// close is traceable from the ticket rather than only from the landing.
func TestTheNoteNamesTheBranchThatSettledIt(t *testing.T) {
l := index.LandingSummary{Branch: "zit-unattended", Target: "trunk"}
note := "Closed by " + l.Branch + " landing on " + l.Target + " (" + shortHash("0e6de71dad35aaaa") + ")"
for _, want := range []string{"zit-unattended", "trunk", "0e6de71dad35"} {
if !strings.Contains(note, want) {
t.Errorf("FAIL: the note does not name %s: %s", want, note)
}
}
}