Skip to content
quarry Artifact [a845e8bfff]
Login

Artifact [a845e8bfff]

Artifact a845e8bfff7c8fbea6641bf62a499856257dbb17d26a3a105f0460fad2a1ff09:


package api

import (
	"context"
	"errors"
	"strings"
	"testing"

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

type fakeCompanions struct {
	bound map[string]string // parent -> store
}

func (f *fakeCompanions) Companion(repo, feature string) (string, bool, error) {
	s, ok := f.bound[repo]
	return s, ok, nil
}

func (f *fakeCompanions) CompanionParent(store string) (string, string, bool, error) {
	for p, s := range f.bound {
		if s == store {
			return p, "forum", true, nil
		}
	}
	return "", "", false, nil
}

func (f *fakeCompanions) BindCompanion(repo, feature, store string) error {
	if f.bound == nil {
		f.bound = map[string]string{}
	}
	f.bound[repo] = store
	return nil
}

// The forum ops resolve through the parent: the reader is checked
// against the parent's name, and the store handed back is the one the
// binding names. An unbound repository is its own forum, as before.
func TestForumRepoForFollowsTheBinding(t *testing.T) {
	fossil, forum := &Repo{}, &Repo{}
	d := Deps{
		Repos:      NewRepos(map[string]*Repo{"fossil": fossil, "fossil-forum": forum, "sqlite": &Repo{}}),
		Companions: &fakeCompanions{bound: map[string]string{"fossil": "fossil-forum"}},
		CanRead: func(_ context.Context, repo string) bool {
			// Only the PARENT's name is ever asked about.
			if repo == "fossil-forum" {
				t.Errorf("FAIL: the gate was asked about the companion %q", repo)
			}
			return repo != "secret"
		},
	}
	ctx := context.Background()
	if r, err := d.forumRepoFor(ctx, "fossil"); err != nil || r != forum {
		t.Errorf("FAIL: fossil's forum resolved to %p (%v), want the companion %p", r, err, forum)
	}
	if r, err := d.forumRepoFor(ctx, "sqlite"); err != nil || r == forum || r == nil {
		t.Errorf("FAIL: an unbound repository did not resolve to itself: %p %v", r, err)
	}
	if _, err := d.forumRepoFor(ctx, "secret"); !errors.Is(err, ops.ErrNotFound) {
		t.Errorf("FAIL: an unreadable parent answered %v, want not found", err)
	}
	// A binding to a store this node does not serve is an operator's
	// problem and says so, rather than "no forum".
	d.Companions = &fakeCompanions{bound: map[string]string{"fossil": "elsewhere"}}
	if _, err := d.forumRepoFor(ctx, "fossil"); err == nil || !strings.Contains(err.Error(), "elsewhere") {
		t.Errorf("FAIL: a binding to an unserved store answered %v", err)
	}
	// A companion is not a project: the listing skips it.
	if d.companionParent("elsewhere") != "fossil" || d.companionParent("fossil") != "" {
		t.Error("FAIL: companionParent does not answer both ways")
	}
	t.Log("ok: forum ops read the parent's gate and the companion's store")
}

// forum.bind refuses every shape RFC 0015 rules out, each with a
// reason, and accepts the same binding twice as the same answer.
func TestForumBindRefusesTheWrongShapes(t *testing.T) {
	comp := &fakeCompanions{bound: map[string]string{"fossil": "fossil-forum"}}
	d := Deps{
		Repos:      NewRepos(map[string]*Repo{"fossil": {}, "fossil-forum": {}, "sqlite": {}, "unindexed": {}}),
		Companions: comp,
	}
	r := ops.NewRegistry()
	if err := registerCompanions(r, d); err != nil {
		t.Fatal(err)
	}
	ctx := context.Background()
	bind := func(repo, store string) error {
		_, err := r.Invoke(ctx, "forum.bind", forumBindIn{Repo: repo, Store: store})
		return err
	}
	cases := []struct{ name, repo, store, want string }{
		{"self", "sqlite", "sqlite", "its own forum"},
		{"unknown parent", "nope", "sqlite", "no repository"},
		{"a companion as parent", "fossil-forum", "sqlite", "itself a companion"},
		{"parent already bound elsewhere", "fossil", "sqlite", "already bound to fossil-forum"},
		{"store already somebody's", "sqlite", "fossil-forum", "already fossil's forum"},
		{"store that has a forum of its own", "sqlite", "fossil", "forum of its own"},
		{"store not indexed yet", "sqlite", "unindexed", "not indexed yet"},
	}
	for _, c := range cases {
		err := bind(c.repo, c.store)
		if err == nil || !strings.Contains(err.Error(), c.want) {
			t.Errorf("FAIL: %s: got %v, want a reason containing %q", c.name, err, c.want)
		}
	}
	if err := bind("fossil", "fossil-forum"); err != nil {
		t.Errorf("FAIL: re-binding the same pair is not idempotent: %v", err)
	}
	if len(comp.bound) != 1 {
		t.Errorf("FAIL: a refused bind was recorded: %v", comp.bound)
	}
	t.Log("ok: every refused shape names its reason; the same binding twice is one binding")
}