Artifact cc8c01ec0a0dec9a739e7500082feba36f21e1c38b0eb54a1e11d93a224d8f9c:
- File
internal/api/register_test.go
— part of check-in
[839e3e756a]
at
2026-08-15 16:13:52
on branch trunk
— search fixes: folded ticket docs, mark snippets, honest limit (050/060/080/130)
search_doc gains label; tickets one folded doc per id; ⟦⟧ markers become <mark> post-escape; default= validate rule fills absent keys. (user: dmestas, size: 9249) [annotate] [blame] [check-ins using]
package api import ( "context" "errors" "path/filepath" "strings" "testing" "craftdesign.group/quarry/internal/artifact" "craftdesign.group/quarry/internal/index" "craftdesign.group/quarry/internal/ops" ) func TestRegisterAllAndVersionOp(t *testing.T) { r := ops.NewRegistry() if err := RegisterAll(r, Deps{Version: "1.2.3-test"}); err != nil { t.Fatal(err) } got, err := r.Invoke(context.Background(), "meta.version", versionIn{}) if err != nil { t.Fatal(err) } if got.(versionOut).Version != "1.2.3-test" { t.Fatalf("got %+v", got) } } func TestRegisteredOpsAppearInSpec(t *testing.T) { r := ops.NewRegistry() if err := RegisterAll(r, Deps{Version: "v"}); err != nil { t.Fatal(err) } spec, err := ops.Spec(r, "quarry", "v") if err != nil { t.Fatal(err) } if spec.Paths["/api/v1/version"].Get == nil { t.Fatal("meta.version missing from generated spec") } } func wikiIndex(t *testing.T) *index.Index { t.Helper() ix, err := index.Open(filepath.Join(t.TempDir(), "index.db")) if err != nil { t.Fatal(err) } t.Cleanup(func() { ix.Close() }) batch := []index.Parsed{ {Hash: "aaa1", Art: &artifact.Artifact{Type: artifact.Wiki, WikiTitle: "Design", Date: "2026-08-15T10:00:00", User: "dan", Mimetype: "text/x-markdown", Body: []byte("# Design\n\ndetails\n")}}, {Hash: "aaa2", Art: &artifact.Artifact{Type: artifact.Wiki, WikiTitle: "Home", Date: "2026-08-15T10:00:01", User: "dan", Mimetype: "text/x-markdown", Body: []byte("See [[Design]] and [[Missing]].\n")}}, {Hash: "aaa3", Art: &artifact.Artifact{Type: artifact.Wiki, WikiTitle: "Home", Date: "2026-08-15T11:00:00", User: "eve", Mimetype: "text/x-markdown", Body: []byte("Revised. See [[Design]].\n")}}, } if err := ix.Fold(batch); err != nil { t.Fatal(err) } return ix } func TestWikiOps(t *testing.T) { r := ops.NewRegistry() if err := RegisterAll(r, Deps{Version: "v", Index: wikiIndex(t)}); err != nil { t.Fatal(err) } got, err := r.Invoke(context.Background(), "wiki.list", wikiListIn{}) if err != nil { t.Fatal(err) } list := got.(wikiListOut) if list.Count != 2 || list.Pages[0].Title != "Design" || list.Pages[1].Title != "Home" { t.Fatalf("list = %+v", list) } if list.Pages[1].User != "eve" { t.Fatalf("latest edit not surfaced: %+v", list.Pages[1]) } got, err = r.Invoke(context.Background(), "wiki.get", wikiGetIn{Name: "Home"}) if err != nil { t.Fatal(err) } page := got.(wikiGetOut) if !strings.Contains(page.HTML, `<a href="/wiki/Design">Design</a>`) { t.Fatalf("existing page not resolved:\n%s", page.HTML) } if !strings.Contains(page.Source, "Revised") { t.Fatalf("stale edit served: %+v", page) } if _, err := r.Invoke(context.Background(), "wiki.get", wikiGetIn{Name: "Missing"}); err == nil { t.Fatal("want error for missing page") } } func TestWikiGetUnresolvedRefStaysText(t *testing.T) { r := ops.NewRegistry() if err := RegisterAll(r, Deps{Version: "v", Index: wikiIndex(t)}); err != nil { t.Fatal(err) } // The older Home revision links [[Missing]]; current revision does // not — fetch Design's referrer instead via a fresh page write. got, err := r.Invoke(context.Background(), "wiki.get", wikiGetIn{Name: "Design"}) if err != nil { t.Fatal(err) } if strings.Contains(got.(wikiGetOut).HTML, "/wiki/Missing") { t.Fatal("nonexistent page linked") } } func TestCheckinGetAndHashResolution(t *testing.T) { ix := wikiIndex(t) h1 := strings.Repeat("ab", 32) h2 := "abcd" + strings.Repeat("ef", 30) if err := ix.Fold([]index.Parsed{ {Hash: h1, Art: &artifact.Artifact{Type: artifact.Manifest, Date: "2026-08-15T09:00:00", User: "dan", Comment: "first"}}, {Hash: h2, Art: &artifact.Artifact{Type: artifact.Manifest, Date: "2026-08-15T09:30:00", User: "dan", Comment: "second", Parents: []string{h1}}}, {Hash: "bbb1", Art: &artifact.Artifact{Type: artifact.Wiki, WikiTitle: "Refs", Date: "2026-08-15T12:00:00", User: "dan", Mimetype: "text/x-markdown", Body: []byte("Fixed in " + h2[:12] + ". Ambiguous: ab.\n")}}, }); err != nil { t.Fatal(err) } r := ops.NewRegistry() if err := RegisterAll(r, Deps{Version: "v", Index: ix}); err != nil { t.Fatal(err) } // Prefix lookup: unique resolves, ambiguous does not. got, err := r.Invoke(context.Background(), "checkin.get", checkinGetIn{Hash: h2[:12]}) if err != nil { t.Fatal(err) } ci := got.(checkinGetOut) if ci.Hash != h2 || len(ci.Parents) != 1 || ci.Parents[0] != h1 { t.Fatalf("checkin = %+v", ci) } if _, err := r.Invoke(context.Background(), "checkin.get", checkinGetIn{Hash: "abcdzz"}); err == nil { t.Fatal("want error for unknown hash") } // Wiki body hash references resolve through the same lookup. got, err = r.Invoke(context.Background(), "wiki.get", wikiGetIn{Name: "Refs"}) if err != nil { t.Fatal(err) } html := got.(wikiGetOut).HTML if !strings.Contains(html, `<a href="/checkin/`+h2+`">`+h2[:12]+`</a>`) { t.Fatalf("hash ref not resolved:\n%s", html) } } func TestTicketOps(t *testing.T) { ix := wikiIndex(t) tid := strings.Repeat("cd", 20) if err := ix.Fold([]index.Parsed{ {Hash: "ccc1", Art: &artifact.Artifact{Type: artifact.Ticket, TicketID: tid, Date: "2026-08-15T09:00:00", User: "dan", Fields: []artifact.Field{{Name: "status", Value: "Open"}, {Name: "title", Value: "crash on empty input"}}}}, {Hash: "ccc2", Art: &artifact.Artifact{Type: artifact.Ticket, TicketID: tid, Date: "2026-08-15T10:00:00", User: "eve", Fields: []artifact.Field{{Name: "resolution", Value: "Fixed"}, {Name: "status", Value: "Closed"}}}}, }); err != nil { t.Fatal(err) } r := ops.NewRegistry() if err := RegisterAll(r, Deps{Version: "v", Index: ix}); err != nil { t.Fatal(err) } got, err := r.Invoke(context.Background(), "ticket.list", ticketListIn{}) if err != nil { t.Fatal(err) } list := got.(ticketListOut) if list.Count != 1 || list.Tickets[0].Status != "Closed" || list.Tickets[0].Title != "crash on empty input" { t.Fatalf("list = %+v", list) } got, err = r.Invoke(context.Background(), "ticket.get", ticketGetIn{ID: tid[:8]}) if err != nil { t.Fatal(err) } tk := got.(ticketGetOut).Ticket if tk.ID != tid || len(tk.Changes) != 2 { t.Fatalf("ticket = %+v", tk) } if fieldValue(tk.Fields, "status") != "Closed" || fieldValue(tk.Fields, "resolution") != "Fixed" { t.Fatalf("fold wrong: %+v", tk.Fields) } if tk.Created != "2026-08-15T09:00:00" || tk.Updated != "2026-08-15T10:00:00" { t.Fatalf("created/updated: %+v", tk) } } func TestSearchOp(t *testing.T) { ix := wikiIndex(t) r := ops.NewRegistry() if err := RegisterAll(r, Deps{Version: "v", Index: ix}); err != nil { t.Fatal(err) } got, err := r.Invoke(context.Background(), "search.query", searchIn{Q: "Design", Limit: 50}) if err != nil { t.Fatal(err) } res := got.(searchOut) if res.Count == 0 { t.Fatalf("no hits: %+v", res) } } func TestEntityNotFoundWrapsSentinel(t *testing.T) { r := ops.NewRegistry() if err := RegisterAll(r, Deps{Version: "v", Index: wikiIndex(t)}); err != nil { t.Fatal(err) } for name, in := range map[string]any{ "wiki.get": wikiGetIn{Name: "NoSuchPage"}, "ticket.get": ticketGetIn{ID: "deadbeef"}, "checkin.get": checkinGetIn{Hash: "deadbeefdeadbeef"}, "checkin.diff": diffGetIn{Hash: "deadbeefdeadbeef"}, } { _, err := r.Invoke(context.Background(), name, in) if !errors.Is(err, ops.ErrNotFound) { t.Errorf("%s: err %v does not wrap ErrNotFound", name, err) } } } func TestSearchTicketDocIsFoldedState(t *testing.T) { // tickets 050 + 130: search over tickets sees CURRENT state with a // human label; stale early-change values don't match at all. ix := wikiIndex(t) tid := strings.Repeat("ef", 20) if err := ix.Fold([]index.Parsed{ {Hash: "ddd1", Art: &artifact.Artifact{Type: artifact.Ticket, TicketID: tid, Date: "2026-08-15T09:00:00", User: "dan", Fields: []artifact.Field{{Name: "status", Value: "Openish"}, {Name: "title", Value: "typo in docs"}}}}, {Hash: "ddd2", Art: &artifact.Artifact{Type: artifact.Ticket, TicketID: tid, Date: "2026-08-15T10:00:00", User: "eve", Fields: []artifact.Field{{Name: "status", Value: "Closedish"}}}}, }); err != nil { t.Fatal(err) } hits, err := ix.Search("typo", 10) if err != nil { t.Fatal(err) } if len(hits) != 1 || hits[0].Label != "typo in docs" { t.Fatalf("hits = %+v", hits) } if !strings.Contains(hits[0].Snippet, "⟦typo⟧") { t.Fatalf("marker missing: %q", hits[0].Snippet) } // The superseded status value is gone from the corpus entirely. if stale, _ := ix.Search("Openish", 10); len(stale) != 0 { t.Fatalf("stale field value still searchable: %+v", stale) } if cur, _ := ix.Search("Closedish", 10); len(cur) != 1 { t.Fatalf("current field value not searchable: %+v", cur) } } func TestSearchLimitZeroIsLiteral(t *testing.T) { // ticket 080: an explicit limit=0 caps to zero results. r := ops.NewRegistry() if err := RegisterAll(r, Deps{Version: "v", Index: wikiIndex(t)}); err != nil { t.Fatal(err) } got, err := r.Invoke(context.Background(), "search.query", searchIn{Q: "Design", Limit: 0}) if err != nil { t.Fatal(err) } if got.(searchOut).Count != 0 { t.Fatalf("limit=0 returned results: %+v", got) } }