Skip to content
quarry Artifact [c09e8e7cec]
Login

Artifact [c09e8e7cec]

Artifact c09e8e7cecb52eda7977299342a75f3399f57e219c74a3cd535477361d49ff21:


package api

import (
	"context"
	"strings"
	"testing"

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

type toldInbox struct{ rows map[string][]AttentionEvent }

func (i *toldInbox) Attend(account string, e AttentionEvent) (uint64, error) {
	if i.rows == nil {
		i.rows = map[string][]AttentionEvent{}
	}
	i.rows[account] = append(i.rows[account], e)
	return uint64(len(i.rows[account])), nil
}
func (i *toldInbox) TailAttention(string, uint64) (<-chan AttentionEvent, func()) {
	return nil, func() {}
}
func (i *toldInbox) AttentionPending(string, uint64) (int, error) { return 0, nil }
func (i *toldInbox) AttentionTip(string) (uint64, error)          { return 0, nil }
func (i *toldInbox) RecentAttention(string, int) ([]AttentionEvent, error) {
	return nil, nil
}

func (i *toldInbox) said(account string) string {
	var b strings.Builder
	for _, e := range i.rows[account] {
		b.WriteString(e.Summary + "\n")
	}
	return b.String()
}

// A repository offer was recorded, readable by both sides, and told to
// nobody. The only way to learn one was waiting was to open the team's
// settings page — the one place somebody with nothing pending has no
// reason to visit (ticket 6d176a8c).
//
// Three tellings, because an offer is a question and it has two answers.
func TestARepositoryOfferTellsBothSides(t *testing.T) {
	rig := func() (*ops.Registry, *oltp.DB, *toldInbox) {
		db, err := oltp.Open(t.TempDir() + "/o.db")
		if err != nil {
			t.Fatal(err)
		}
		t.Cleanup(func() { db.Close() })
		for _, who := range []string{"lou", "ann"} {
			if err := db.AddAccount(who, "a-long-enough-password", false); err != nil {
				t.Fatal(err)
			}
		}
		inbox := &toldInbox{}
		r := ops.NewRegistry()
		if err := RegisterAll(r, Deps{Version: "v", Repos: testRepos(wikiIndex(t)),
			Teams: db, Accounts: acctAdapter{db}, Attention: inbox,
			CapsFor: func(a, repo string) CapSet { return db.EffectiveCaps(a, repo) }}); err != nil {
			t.Fatal(err)
		}
		return r, db, inbox
	}
	lou := ops.WithUser(context.Background(), "lou") // team owner
	ann := ops.WithUser(context.Background(), "ann") // repo admin, not a member

	// OFFERED: the owner who must decide is told.
	r, db, inbox := rig()
	if _, err := r.Invoke(lou, "team.create", teamCreateIn{Name: "core"}); err != nil {
		t.Fatal(err)
	}
	if err := db.SetCaps("ann", "test", "a"); err != nil {
		t.Fatal(err)
	}
	if _, err := r.Invoke(ann, "team.repo.add", teamRepoIn{Name: "core", Repo: "test"}); err != nil {
		t.Fatal(err)
	}
	if got := inbox.said("lou"); !strings.Contains(got, "ann offered test to core") {
		t.Errorf("FAIL: the owner was not told an offer is waiting: %q", got)
	} else {
		t.Log("ok: the owner who must decide is told")
	}
	if inbox.said("ann") != "" {
		t.Errorf("FAIL: the offerer was told about their own action: %q", inbox.said("ann"))
	} else {
		t.Log("ok: nobody is told what they just did themselves")
	}

	// ACCEPTED: the offerer gets their answer.
	if _, err := r.Invoke(lou, "team.repo.offer.accept", teamRepoIn{Name: "core", Repo: "test"}); err != nil {
		t.Fatal(err)
	}
	if got := inbox.said("ann"); !strings.Contains(got, "lou accepted test into core") {
		t.Errorf("FAIL: the offerer was not told it was accepted: %q", got)
	} else {
		t.Log("ok: an accepted offer answers the person who asked")
	}

	// DECLINED: the same question, answered the other way.
	r2, db2, inbox2 := rig()
	if _, err := r2.Invoke(lou, "team.create", teamCreateIn{Name: "core"}); err != nil {
		t.Fatal(err)
	}
	if err := db2.SetCaps("ann", "test", "a"); err != nil {
		t.Fatal(err)
	}
	if _, err := r2.Invoke(ann, "team.repo.add", teamRepoIn{Name: "core", Repo: "test"}); err != nil {
		t.Fatal(err)
	}
	if _, err := r2.Invoke(lou, "team.repo.offer.decline", teamRepoIn{Name: "core", Repo: "test"}); err != nil {
		t.Fatal(err)
	}
	if got := inbox2.said("ann"); !strings.Contains(got, "lou declined test for core") {
		t.Errorf("FAIL: the offerer was not told it was declined: %q", got)
	} else {
		t.Log("ok: a declined offer answers too, rather than going quiet")
	}
}