package api
import (
"context"
"path/filepath"
"strings"
"testing"
"craftdesign.group/quarry/internal/oltp"
"craftdesign.group/quarry/internal/ops"
)
// An owner watching /t/{name}/settings wakes on the team noun. An offer
// arriving is the strongest reason to wake them — it is a decision
// waiting on them, made by somebody else, on a page they already have
// open — and it raised nothing at all: the offer path returned before
// any raise, so the only thing that reached the stream was its own
// heartbeat.
//
// Withdrawing has the same shape from the other side: the row is gone
// and the owner is looking at a control for a decision that is no
// longer theirs to make.
func TestAnOfferAndItsWithdrawalRaiseTheTeamNoun(t *testing.T) {
db, err := oltp.Open(filepath.Join(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)
}
}
ev := &fakeEvents{}
r := ops.NewRegistry()
if err := RegisterAll(r, Deps{Version: "v",
Repos: testRepos(wikiIndex(t)),
Teams: db,
Accounts: acctAdapter{db},
Events: ev,
CapsFor: func(a, repo string) CapSet { return db.EffectiveCaps(a, repo) },
}); err != nil {
t.Fatal(err)
}
lou := ops.WithUser(context.Background(), "lou")
ann := ops.WithUser(context.Background(), "ann")
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)
}
// ann administers the repository but does not own the team, so this
// is an offer rather than a move.
ev.reset()
if _, err := r.Invoke(ann, "team.repo.add",
teamRepoIn{Name: "core", Repo: "test"}); err != nil {
t.Fatal(err)
}
if !ev.sawNoun("core", "team") {
t.Errorf("FAIL: an offer raised nothing; an owner's open page never wakes. saw %v", ev.subjects())
} else {
t.Log("ok: the offer wakes the team")
}
ev.reset()
if _, err := r.Invoke(ann, "team.repo.offer.decline",
teamRepoIn{Name: "core", Repo: "test"}); err != nil {
t.Fatal(err)
}
if !ev.sawNoun("core", "team") {
t.Errorf("FAIL: a withdrawal raised nothing. saw %v", ev.subjects())
} else {
t.Log("ok: the withdrawal wakes the team")
}
}
func (f *fakeEvents) reset() {
f.mu.Lock()
defer f.mu.Unlock()
f.seen = nil
}
// sawNoun asks the question the stream asks: the team's name sits in
// the repository slot of the subject, and WatchNoun matches on the
// noun. Not the whole subject, because which team.* action it was is
// the payload's business and not this guard's.
func (f *fakeEvents) sawNoun(team, noun string) bool {
for _, s := range f.subjects() {
if strings.Contains(s, "."+team+".") && strings.Contains(s, "."+noun+".") {
return true
}
}
return false
}