package api
import (
"context"
"path/filepath"
"strings"
"testing"
"craftdesign.group/quarry/internal/artifact"
"craftdesign.group/quarry/internal/index"
)
// forumStore folds n threads into a fresh index.
func forumStore(t *testing.T, n int) *Repo {
t.Helper()
ix, err := index.Open(filepath.Join(t.TempDir(), "i.db"))
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { ix.Close() })
var batch []index.Parsed
for i := 0; i < n; i++ {
h := strings.Repeat("ab", 31) + string(rune('0'+i%10)) + string(rune('a'+i/10))
batch = append(batch, index.Parsed{Hash: h, Art: &artifact.Artifact{
Type: artifact.Forum, ThreadTitle: "thread", Date: "2026-09-01T10:00:00", User: "dan",
}})
}
if len(batch) > 0 {
if err := ix.FoldNow(batch); err != nil {
t.Fatal(err)
}
}
return &Repo{Index: ix}
}
// The forum tab must count what the forum PAGE shows. On zeitforge the
// badge read 4 — fossil's own legacy threads — beside a page listing
// 3,558 from the companion, and a number that disagrees with the page
// it labels is worse than no number at all.
func TestTheForumCountIncludesTheCompanion(t *testing.T) {
parent, companion := forumStore(t, 2), forumStore(t, 5)
d := Deps{
Repos: NewRepos(map[string]*Repo{"fossil": parent, "fossil-forum": companion}),
Companions: &fakeCompanions{bound: map[string]string{"fossil": "fossil-forum"}},
}
if got := d.companionForumThreads(context.Background(), "fossil"); got != 5 {
t.Errorf("FAIL: the companion contributed %d threads, want 5", got)
}
// An unbound repository contributes nothing from anywhere else.
if got := d.companionForumThreads(context.Background(), "fossil-forum"); got != 0 {
t.Errorf("FAIL: an unbound store borrowed %d threads", got)
}
}
// A binding that names a store this node does not serve must not break
// the counts op — every repository page calls it, and one misconfigured
// binding would take out a nav that is otherwise fine. The forum page
// itself still says so properly, which is where it belongs.
func TestABrokenBindingDoesNotBreakTheCounts(t *testing.T) {
d := Deps{
Repos: NewRepos(map[string]*Repo{"fossil": forumStore(t, 2)}),
Companions: &fakeCompanions{bound: map[string]string{"fossil": "nowhere"}},
}
if got := d.companionForumThreads(context.Background(), "fossil"); got != 0 {
t.Errorf("FAIL: a dangling binding counted %d", got)
}
// And the forum page still refuses in an operator's words.
if _, err := d.forumRepoFor(context.Background(), "fossil"); err == nil {
t.Error("FAIL: a dangling binding resolved to something")
}
}
// A forge with no binding table at all behaves exactly as before.
func TestNoCompanionPlaneIsHarmless(t *testing.T) {
d := Deps{Repos: NewRepos(map[string]*Repo{"fossil": forumStore(t, 2)})}
if got := d.companionForumThreads(context.Background(), "fossil"); got != 0 {
t.Errorf("FAIL: got %d with no companion plane", got)
}
}