package api
import (
"context"
"path/filepath"
"strings"
"testing"
"craftdesign.group/quarry/internal/artifact"
"craftdesign.group/quarry/internal/index"
"craftdesign.group/quarry/internal/oltp"
"craftdesign.group/quarry/internal/ops"
)
// A ticket filed on this forge is found by search before it is ever
// promoted to an artifact. The index has never heard of it, so a search
// for words in its title found nothing — every recent ticket on a forge
// that files locally. A title match leads; a word only in the body
// still finds it, after the index's hits. A scope that is not tickets
// leaves it out, and the fleet search finds it too.
func TestSearchFindsTicketsFiledHere(t *testing.T) {
ix, err := index.Open(filepath.Join(t.TempDir(), "i.db"))
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { ix.Close() })
stored := "aaaa000000000000000000000000000000000000000000000000000000000001"
if err := ix.FoldNow([]index.Parsed{{Hash: stored, Art: &artifact.Artifact{Type: artifact.Ticket,
TicketID: stored, Date: "2026-09-01T10:00:00", User: "dan",
Fields: []artifact.Field{{Name: "title", Value: "merge view in the old tracker"}, {Name: "status", Value: "Open"}}}}}); err != nil {
t.Fatal(err)
}
db, err := oltp.Open(filepath.Join(t.TempDir(), "o.db"))
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { db.Close() })
r := ops.NewRegistry()
if err := RegisterAll(r, Deps{Version: "v", Repos: NewRepos(map[string]*Repo{"q": {Index: ix}}),
Local: db, Idem: db, Numbers: db}); err != nil {
t.Fatal(err)
}
ctx := ops.WithUser(context.Background(), "dan")
file := func(title, comment string) string {
out, err := r.Invoke(ctx, "ticket.intake", ticketIntakeIn{Repo: "q", Title: title, Comment: comment})
if err != nil {
t.Fatal(err)
}
return out.(ticketIntakeOut).ID
}
panes := file("A conflicting landing shows its merge in three panes", "Meld-style view.")
body := file("Resolve in the browser", "Builds on the three panes view.")
search := func(q, kind string) []index.SearchResult {
out, err := r.Invoke(ctx, "search.query", searchIn{Repo: "q", Q: q, Kind: kind, Limit: 50})
if err != nil {
t.Fatal(err)
}
return out.(searchOut).Results
}
got := search("three panes", "")
if len(got) != 2 || got[0].Ref != panes || got[1].Ref != body {
t.Fatalf("FAIL: three panes found %v, want the titled ticket then the one that says it in its body", refs(got))
}
if got[0].Number == 0 || got[0].Label == "" {
t.Errorf("FAIL: a local hit carries no number or title: %+v", got[0])
}
if !strings.Contains(got[1].Snippet, "⟦three⟧") {
t.Errorf("FAIL: a body hit's snippet does not mark the word: %q", got[1].Snippet)
}
if got := search("merge", "ticket"); len(got) != 2 || got[0].Ref != panes || got[1].Ref != stored {
t.Errorf("FAIL: merge found %v, want the local title match ahead of the stored ticket", refs(got))
}
if got := search("three panes", "checkin"); len(got) != 0 {
t.Errorf("FAIL: a check-in search offered tickets: %v", refs(got))
}
out, err := r.Invoke(ctx, "search.all", searchAllIn{Q: "three panes", Limit: 50})
if err != nil {
t.Fatal(err)
}
all := out.(searchAllOut)
if len(all.Results) != 2 || all.Results[0].Ref != panes || len(all.ByRepo) != 1 || all.ByRepo[0].Count != 2 {
t.Errorf("FAIL: the fleet search found %d (%v), counted %v", len(all.Results), all.Results, all.ByRepo)
}
}
func refs(rs []index.SearchResult) []string {
var out []string
for _, r := range rs {
out = append(out, r.Ref[:min(8, len(r.Ref))])
}
return out
}