package api
import (
"context"
"path/filepath"
"strings"
"testing"
"craftdesign.group/quarry/internal/artifact"
"craftdesign.group/quarry/internal/index"
"craftdesign.group/quarry/internal/ops"
)
// repo.get WAS ANSWERING ONE OF ITS TWO NUMBERS.
//
// It reported contributors: 0 against a repository with 2027 check-ins,
// on every repository, because it filled Checkins from CheckinCount and
// never touched Contributors at all — the field stayed the zero value it
// was born with. repo.list filled BOTH, from index.Summary, so the two
// ops disagreed about the same repository: the fleet listing showed the
// people who put a store together and the store's own page showed none.
//
// A number that is wrong in a direction nobody can act on is worse than
// an absent one, which is why this pins the VALUE rather than the
// field's presence: 0 contributors and 2 contributors are both "a
// number in the field".
//
// authoredStore folds a fresh index holding n check-ins by the given
// authors, so the expectation is arithmetic rather than a fixture.
func authoredStore(t *testing.T, n int, authors ...string) *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("cd", 31) + string(rune('0'+i%10)) + string(rune('a'+i/10))
batch = append(batch, index.Parsed{Hash: h, Art: &artifact.Artifact{
Type: artifact.Manifest, Date: "2026-09-01T10:00:00", User: authors[i%len(authors)],
Comment: "check-in",
}})
}
if len(batch) > 0 {
if err := ix.FoldNow(batch); err != nil {
t.Fatal(err)
}
}
return &Repo{Index: ix}
}
// The repository's own page says how many people put it together, and it
// says the same thing the fleet row says.
func TestRepoGetReportsTheContributorsItHas(t *testing.T) {
repo := authoredStore(t, 6, "dan", "jva")
r := ops.NewRegistry()
if err := RegisterAll(r, Deps{Version: "v", Repos: NewRepos(map[string]*Repo{"demo": repo})}); err != nil {
t.Fatal(err)
}
out, err := r.Invoke(context.Background(), "repo.get", repoGetIn{Repo: "demo"})
if err != nil {
t.Fatal(err)
}
got := out.(repoInfo)
if got.Contributors != 2 {
t.Errorf("repo.get says contributors = %d for a store two people authored, want 2: "+
"a repository page showing nobody is the number this whole op exists to avoid",
got.Contributors)
}
// And the neighbour it was already answering is unmoved: Summary's
// check-in count is the same query CheckinCount ran.
if got.Checkins != 6 {
t.Errorf("repo.get says checkins = %d, want 6", got.Checkins)
}
}
// The two ops that describe a repository must agree about it. They are
// read by different pages — the fleet listing and the repository home —
// and a reader who sees both has no way to tell which one is lying.
func TestRepoGetAndRepoListAgreeAboutAContributorCount(t *testing.T) {
repo := authoredStore(t, 4, "dan", "jva", "jan")
d := Deps{Version: "v", Repos: NewRepos(map[string]*Repo{"demo": repo})}
r := ops.NewRegistry()
if err := RegisterAll(r, d); err != nil {
t.Fatal(err)
}
one, err := r.Invoke(context.Background(), "repo.get", repoGetIn{Repo: "demo"})
if err != nil {
t.Fatal(err)
}
list, err := r.Invoke(context.Background(), "repo.list", repoListIn{})
if err != nil {
t.Fatal(err)
}
rows := list.(repoListOut).Repos
if len(rows) != 1 {
t.Fatalf("repo.list returned %d rows, want 1", len(rows))
}
single, listed := one.(repoInfo), rows[0]
if single.Contributors != listed.Contributors {
t.Errorf("repo.get says %d contributors and repo.list says %d about the same repository",
single.Contributors, listed.Contributors)
}
if single.Checkins != listed.Checkins {
t.Errorf("repo.get says %d check-ins and repo.list says %d about the same repository",
single.Checkins, listed.Checkins)
}
}