Skip to content
quarry Artifact [cc9905cd66]
Login

Artifact [cc9905cd66]

Artifact cc9905cd66e4862687c22c587f6db44d8f4f43cb25a87ae5a45cc61f3a4a2cee:


package api

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

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

// Every row of repo.list and me.repos, and repo.get, says where the
// repository appears: handle, namespace and address, the same as
// repo.add answered when it was made (quarry#422). A repository in a
// person's namespace and one in a team's agree with repo.add; a private
// repository stays out of an anonymous list.
func TestRepositoryIdentityInListsAndGet(t *testing.T) {
	db, err := oltp.Open(filepath.Join(t.TempDir(), "o.db"))
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { db.Close() })
	if err := db.AddAccount("ada", "a-long-enough-password", false); err != nil {
		t.Fatal(err)
	}
	if err := db.AddTeam("crew", "Crew", "", "ada"); err != nil {
		t.Fatal(err)
	}
	shown := map[string][2]string{
		"mine":   {"ada", "mine"},
		"theirs": {"crew", "theirs"},
		"secret": {"ada", "secret"},
	}
	address := func(key string) (string, string, bool) {
		a, ok := shown[key]
		return a[0], a[1], ok
	}
	reg := &fakeRegistry{}
	repos := NewRepos(map[string]*Repo{
		"mine": {Index: wikiIndex(t)}, "theirs": {Index: wikiIndex(t)}, "secret": {Index: wikiIndex(t)},
	})
	r := ops.NewRegistry()
	if err := RegisterAll(r, Deps{Version: "v", Repos: repos, Registry: reg, Address: address,
		Accounts: stubAccounts{grants: map[string]map[string]string{
			"ada": {"mine": "admin", "theirs": "read", "secret": "admin"}}},
		Teams:   db,
		CanRead: func(ctx context.Context, name string) bool { return name != "secret" || ops.User(ctx) == "ada" },
	}); err != nil {
		t.Fatal(err)
	}
	as := ops.WithUser(context.Background(), "ada")

	// What repo.add answers for each.
	mineAdd, err := r.Invoke(as, "repo.add", repoAddIn{Name: "mine", Create: true})
	if err != nil {
		t.Fatal(err)
	}
	theirsAdd, err := r.Invoke(as, "repo.add", repoAddIn{Name: "theirs", Namespace: "crew", Create: true})
	if err != nil {
		t.Fatal(err)
	}
	want := map[string]repoAddOut{"mine": mineAdd.(repoAddOut), "theirs": theirsAdd.(repoAddOut)}

	agree := func(where, key, handle, ns, addr string) {
		t.Helper()
		w, ok := want[key]
		if !ok {
			return
		}
		if handle != w.Handle || ns != w.Namespace || addr != w.Address {
			t.Errorf("FAIL: %s: %s is %q/%q/%q, repo.add said %q/%q/%q", where, key, handle, ns, addr, w.Handle, w.Namespace, w.Address)
		}
		if key != w.Name {
			t.Errorf("FAIL: %s: name %q is not repo.add's key %q", where, key, w.Name)
		}
	}

	got, err := r.Invoke(as, "repo.list", repoListIn{})
	if err != nil {
		t.Fatal(err)
	}
	seen := map[string]bool{}
	for _, row := range got.(repoListOut).Repos {
		seen[row.Name] = true
		if row.Handle == "" || row.Address == "" {
			t.Errorf("FAIL: repo.list row %q lacks handle or address: %+v", row.Name, row)
		}
		agree("repo.list", row.Name, row.Handle, row.Namespace, row.Address)
	}
	if !seen["mine"] || !seen["theirs"] || !seen["secret"] {
		t.Errorf("FAIL: the owner's list is missing a repository: %v", seen)
	}

	got, err = r.Invoke(as, "me.repos", meReposIn{})
	if err != nil {
		t.Fatal(err)
	}
	if n := len(got.(meReposOut).Repos); n != 3 {
		t.Errorf("FAIL: me.repos has %d rows, want 3", n)
	}
	for _, row := range got.(meReposOut).Repos {
		if row.Handle == "" || row.Address == "" {
			t.Errorf("FAIL: me.repos row %q lacks handle or address: %+v", row.Name, row)
		}
		agree("me.repos", row.Name, row.Handle, row.Namespace, row.Address)
	}

	for key := range want {
		info, err := r.Invoke(as, "repo.get", repoGetIn{Repo: key})
		if err != nil {
			t.Fatal(err)
		}
		i := info.(repoInfo)
		agree("repo.get", key, i.Handle, i.Namespace, i.Address)
	}

	// No change to who sees what: an anonymous list omits the private one.
	got, err = r.Invoke(context.Background(), "repo.list", repoListIn{})
	if err != nil {
		t.Fatal(err)
	}
	for _, row := range got.(repoListOut).Repos {
		if row.Name == "secret" {
			t.Errorf("FAIL: an anonymous list shows a private repository: %+v", row)
		}
	}
	if n := len(got.(repoListOut).Repos); n != 2 {
		t.Errorf("FAIL: an anonymous list has %d rows, want 2", n)
	}
	t.Log("ok")
}

// A key the address book does not know appears by itself, in no
// namespace, which is how repoLabel already reads it.
func TestRepositoryIdentityWithoutAnAddressBook(t *testing.T) {
	r := ops.NewRegistry()
	if err := RegisterAll(r, Deps{Version: "v", Repos: NewRepos(map[string]*Repo{"solo": {Index: wikiIndex(t)}})}); err != nil {
		t.Fatal(err)
	}
	got, err := r.Invoke(context.Background(), "repo.get", repoGetIn{Repo: "solo"})
	if err != nil {
		t.Fatal(err)
	}
	if i := got.(repoInfo); i.Handle != "solo" || i.Namespace != "" || i.Address != "solo" {
		t.Errorf("FAIL: %+v", i)
	}
	t.Log("ok")
}