Skip to content
quarry Artifact [7941289931]
Login

Artifact [7941289931]

Artifact 79412899319e4122c2c5729782d634ce1e0e3167ab1fcc6b2ca840ce21db4e2f:


package api

import (
	"context"
	"path/filepath"
	"strings"
	"testing"

	"craftdesign.group/quarry/internal/artifact"
	"craftdesign.group/quarry/internal/index"
	"craftdesign.group/quarry/internal/ops"
)

// forumRepoAt folds one forum thread so a store has activity to report.
func forumRepoAt(t *testing.T, seed, date, title 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() })
	if err := ix.FoldNow([]index.Parsed{{Hash: strings.Repeat(seed, 32),
		Art: &artifact.Artifact{Type: artifact.Forum, ThreadTitle: title,
			Date: date, User: "dan"}}}); err != nil {
		t.Fatal(err)
	}
	return &Repo{Index: ix}
}

func dashFeed(t *testing.T, d Deps) meFeedOut {
	t.Helper()
	r := ops.NewRegistry()
	if err := RegisterAll(r, d); err != nil {
		t.Fatal(err)
	}
	got, err := r.Invoke(ops.WithUser(context.Background(), "dan"), "me.feed", meFeedIn{})
	if err != nil {
		t.Fatal(err)
	}
	return got.(meFeedOut)
}

// A companion is a store, not a project (RFC 0015): Fleet lists it with
// its parent, and no other list does. repo.list has filtered them since
// the binding existed — the dashboard is the OTHER list of repositories
// and had not, so a site admin's landing page showed "fossil-forum" as
// though it were a project of its own.
func TestTheDashboardDoesNotListACompanion(t *testing.T) {
	out := dashFeed(t, Deps{Version: "v",
		Repos: NewRepos(map[string]*Repo{
			"fossil":       forumRepoAt(t, "a", "2026-09-01T10:00:00", "parent thread"),
			"fossil-forum": forumRepoAt(t, "b", "2026-09-02T10:00:00", "companion thread"),
		}),
		Companions: &fakeCompanions{bound: map[string]string{"fossil": "fossil-forum"}},
		Accounts: stubAccounts{grants: map[string]map[string]string{
			"dan": {"fossil": "admin", "fossil-forum": "admin"},
		}}})

	for _, r := range out.Repos {
		if r.Name == "fossil-forum" {
			t.Errorf("FAIL: the dashboard listed the companion as a project: %+v", out.Repos)
		}
	}
	if len(out.Repos) != 1 || out.Repos[0].Name != "fossil" {
		t.Errorf("FAIL: repos = %+v, want fossil alone", out.Repos)
	}
}

// And its activity is the parent's: a thread in fossil-forum is
// something that happened to fossil, which is what the RFC means by
// attributing forum activity to the parent.
func TestCompanionActivityIsAttributedToTheParent(t *testing.T) {
	out := dashFeed(t, Deps{Version: "v",
		Repos: NewRepos(map[string]*Repo{
			"fossil":       forumRepoAt(t, "a", "2026-09-01T10:00:00", "parent thread"),
			"fossil-forum": forumRepoAt(t, "b", "2026-09-02T10:00:00", "companion thread"),
		}),
		Companions: &fakeCompanions{bound: map[string]string{"fossil": "fossil-forum"}},
		Accounts: stubAccounts{grants: map[string]map[string]string{
			"dan": {"fossil": "admin", "fossil-forum": "admin"},
		}}})

	if len(out.Feed) == 0 {
		t.Fatal("FAIL: the feed is empty; the companion's activity was dropped rather than attributed")
	}
	for _, e := range out.Feed {
		if e.Repo == "fossil-forum" {
			t.Errorf("FAIL: a feed row is labelled with the companion: %+v", e)
		}
	}
	// The companion's thread is the newest, so it leads — under the
	// parent's name.
	if out.Feed[0].Repo != "fossil" {
		t.Errorf("FAIL: the newest row is under %q, want fossil", out.Feed[0].Repo)
	}
}

// An unbound forge is untouched: every granted repository is its own
// project and keeps its own name.
func TestWithoutABindingEveryRepositoryIsItsOwnProject(t *testing.T) {
	out := dashFeed(t, Deps{Version: "v",
		Repos: NewRepos(map[string]*Repo{
			"fossil":       forumRepoAt(t, "a", "2026-09-01T10:00:00", "one"),
			"fossil-forum": forumRepoAt(t, "b", "2026-09-02T10:00:00", "two"),
		}),
		Accounts: stubAccounts{grants: map[string]map[string]string{
			"dan": {"fossil": "admin", "fossil-forum": "admin"},
		}}})
	if len(out.Repos) != 2 {
		t.Errorf("FAIL: repos = %+v, want both listed when nothing is bound", out.Repos)
	}
}