package main
import (
"context"
"errors"
"fmt"
"strings"
"testing"
"time"
"craftdesign.group/quarry/internal/api"
"craftdesign.group/quarry/internal/errlog"
"craftdesign.group/quarry/internal/notify"
"craftdesign.group/quarry/internal/oltp"
)
type fakePlane struct {
subs []string
emails []string
topics map[string]string
added map[string][]string
removed map[string][]string
failSub string
prefs map[string]notify.SubscriberPreferences
setp map[string][]map[string]map[string]bool
}
func newFakePlane() *fakePlane {
return &fakePlane{topics: map[string]string{},
added: map[string][]string{}, removed: map[string][]string{}}
}
func (f *fakePlane) EnsureSubscriber(_ context.Context, account, email string) error {
if account == f.failSub {
return errors.New("notify is not holding this one")
}
f.subs = append(f.subs, account)
f.emails = append(f.emails, email)
return nil
}
func (f *fakePlane) EnsureTopic(_ context.Context, key, name string) error {
f.topics[key] = name
return nil
}
func (f *fakePlane) AddTopicMember(_ context.Context, key, account string) error {
f.added[key] = append(f.added[key], account)
return nil
}
func (f *fakePlane) RemoveTopicMember(_ context.Context, key, account string) error {
f.removed[key] = append(f.removed[key], account)
return nil
}
func (f *fakePlane) Preferences(_ context.Context, account string) (notify.SubscriberPreferences, error) {
return f.prefs[account], nil
}
func (f *fakePlane) SetPreferences(_ context.Context, account string, wf map[string]map[string]bool, _ map[string]*notify.DigestWindow) error {
if f.setp == nil {
f.setp = map[string][]map[string]map[string]bool{}
}
f.setp[account] = append(f.setp[account], wf)
return nil
}
type fakeBook struct {
accounts []oltp.Account
grants map[string]map[string]oltp.Role
repos []string
}
func (b fakeBook) Repos() ([]oltp.Repo, error) {
out := make([]oltp.Repo, 0, len(b.repos))
for _, n := range b.repos {
out = append(out, oltp.Repo{Name: n})
}
return out, nil
}
func (b fakeBook) Accounts() ([]oltp.Account, error) { return b.accounts, nil }
func (b fakeBook) GrantsFor(account string) (map[string]oltp.Role, error) {
return b.grants[account], nil
}
func has(list []string, want string) bool {
for _, s := range list {
if s == want {
return true
}
}
return false
}
// The sweep makes notify match the forge: every account is a subscriber,
// the admin group holds today's admins and nobody else, and a grant is a
// membership of that repository's topic — whether or not the write that
// created it ever provisioned anything.
func TestReconcileMatchesAccountsAndGrants(t *testing.T) {
np := newFakePlane()
book := fakeBook{
accounts: []oltp.Account{
{Name: "dmestas", Admin: true},
{Name: "lou"},
{Name: "ghost", Admin: true, Disabled: "2026-09-01T00:00:00"},
},
grants: map[string]map[string]oltp.Role{
"lou": {"quarry": oltp.Read, "Synthetic": oltp.Read},
"ghost": {"quarry": oltp.Read},
"dmestas": {"Synthetic": oltp.RepoAdmin},
},
// fossil is granted to nobody, which is exactly the shape that
// had no topic.
repos: []string{"quarry", "Synthetic", "fossil"},
}
sync, err := reconcileNotify(context.Background(), np, book)
if err != nil {
t.Fatalf("FAIL: %v", err)
}
for _, name := range []string{"dmestas", "lou", "ghost"} {
if !has(np.subs, name) {
t.Errorf("FAIL: %s was not ensured as a subscriber", name)
}
}
if !has(np.added[adminsTopic], "dmestas") {
t.Error("FAIL: the admin did not join the admin group")
}
if !has(np.removed[adminsTopic], "lou") {
t.Error("FAIL: a non-admin was left in the admin group")
}
if !has(np.removed[adminsTopic], "ghost") || has(np.added[adminsTopic], "ghost") {
t.Error("FAIL: a disabled admin still receives fleet alerts")
}
if _, ok := np.topics[adminsTopic]; !ok {
t.Error("FAIL: the admin group topic was never created")
}
// A grant is a membership, and the key is the repository's, never
// the raw name: notify's keys are lowercase and punctuation-free,
// so "Synthetic" and "synthetic" must not become two topics.
if _, ok := np.topics[repoTopicKey("quarry")]; !ok {
t.Error("FAIL: the repository's activity topic was never created")
}
if !has(np.added[repoTopicKey("quarry")], "lou") {
t.Error("FAIL: a granted account did not join the repository topic")
}
if !has(np.added[repoTopicKey("Synthetic")], "lou") {
t.Error("FAIL: the second grant was skipped")
}
// A shut account keeps its rows in the forge; it must not keep its
// notifications.
if has(np.added[repoTopicKey("quarry")], "ghost") {
t.Error("FAIL: a disabled account was subscribed to repository activity")
}
// A repository nobody was granted still has somewhere to deliver.
if _, ok := np.topics[repoTopicKey("fossil")]; !ok {
t.Error("FAIL: a repository with no grants got no topic")
}
// A site admin hears what their grants reach and nothing else
// (#318), and the sweep takes back the topics an earlier build
// joined them to for holding every repository.
if !has(np.added[repoTopicKey("Synthetic")], "dmestas") {
t.Error("FAIL: the site admin is not in the topic of a repository they were granted")
}
for _, repo := range []string{"quarry", "fossil"} {
if has(np.added[repoTopicKey(repo)], "dmestas") {
t.Errorf("FAIL: the site admin joined %s's topic with no grant there", repo)
}
if !has(np.removed[repoTopicKey(repo)], "dmestas") {
t.Errorf("FAIL: the site admin was left in %s's topic", repo)
}
}
if sync.Subscribers != 3 || sync.Members != 4 {
t.Errorf("FAIL: counted %d subscribers and %d memberships, want 3 and 4",
sync.Subscribers, sync.Members)
}
t.Log("ok: subscribers, the admin group and every grant's topic are current")
}
// One account notify cannot hold must not cost the others their alerts.
func TestReconcileKeepsGoingPastOneFailure(t *testing.T) {
np := newFakePlane()
np.failSub = "lou"
book := fakeBook{
accounts: []oltp.Account{{Name: "lou"}, {Name: "dmestas", Admin: true}},
repos: []string{"quarry"},
}
sync, err := reconcileNotify(context.Background(), np, book)
if err == nil {
t.Error("FAIL: the failure was swallowed")
}
if !has(np.added[adminsTopic], "dmestas") || sync.Subscribers != 1 {
t.Error("FAIL: the sweep stopped at the first failure")
}
if has(np.added[adminsTopic], "lou") || has(np.removed[adminsTopic], "lou") {
t.Error("FAIL: an account notify does not hold was added to a topic anyway")
}
t.Log("ok: a failed account is reported and the rest are still provisioned")
}
type fakeAttention struct {
sent map[string][]api.AttentionEvent
}
func (f *fakeAttention) Attend(account string, e api.AttentionEvent) (uint64, error) {
if f.sent == nil {
f.sent = map[string][]api.AttentionEvent{}
}
f.sent[account] = append(f.sent[account], e)
return uint64(len(f.sent[account])), nil
}
func (f *fakeAttention) TailAttention(string, uint64) (<-chan api.AttentionEvent, func()) {
return nil, func() {}
}
func (f *fakeAttention) AttentionPending(string, uint64) (int, error) { return 0, nil }
func (f *fakeAttention) AttentionTip(string) (uint64, error) { return 0, nil }
func (f *fakeAttention) MarkAttentionRead(string, uint64) error { return nil }
func (f *fakeAttention) AttentionReadSeq(string) uint64 { return 0 }
func (f *fakeAttention) RecentAttention(string, int) ([]api.AttentionEvent, error) {
return nil, nil
}
// A fault reaches the admins through the forge's own inbox, which does
// not depend on notify being present or able to deliver.
func TestAdminAlertReachesEveryAdminsInbox(t *testing.T) {
att := &fakeAttention{}
book := fakeBook{accounts: []oltp.Account{
{Name: "dmestas", Admin: true},
{Name: "lou"},
{Name: "ghost", Admin: true, Disabled: "2026-09-01T00:00:00"},
}}
first := time.Date(2026, 9, 8, 1, 30, 0, 0, time.UTC)
alertAdmins(book, att, errlog.Entry{Surface: "ingest", Repo: "quarry",
Message: "ingest lane is full", First: first}, func(string, string) bool { return true })
got := att.sent["dmestas"]
if len(got) != 1 {
t.Fatalf("FAIL: the admin got %d rows, want 1", len(got))
}
if got[0].Kind != "error" || got[0].Repo != "quarry" {
t.Errorf("FAIL: the row does not describe the fault: %+v", got[0])
}
if !strings.Contains(got[0].Summary, "ingest lane is full") {
t.Error("FAIL: the row does not carry what went wrong")
}
if got[0].Link == "" {
t.Error("FAIL: the row does not lead anywhere")
}
// The idempotency key is what pairs this row with notify's twin, so
// one fault is one line in the inbox rather than two.
if got[0].TxnID != "err:"+first.Format(time.RFC3339Nano) {
t.Errorf("FAIL: the row does not pair with its notify twin: %q", got[0].TxnID)
}
if len(att.sent["lou"]) != 0 {
t.Error("FAIL: a fleet alert reached a non-admin")
}
if len(att.sent["ghost"]) != 0 {
t.Error("FAIL: a disabled admin is still alerted")
}
t.Log("ok: every serving admin gets the fault in their forge inbox")
}
// Email is off until chosen. The sweep answers "no" for every family a
// person has not answered, and never touches a cell they set — on or
// off — nor anything but the email cell.
func TestEmailIsOffUntilChosen(t *testing.T) {
np := newFakePlane()
np.prefs = map[string]notify.SubscriberPreferences{
"lou": {Workflows: map[string]map[string]bool{
"quarry-mention": {"email": true, "push": false},
"quarry-forum-reply": {"email": false},
}},
}
book := fakeBook{accounts: []oltp.Account{{Name: "dmestas", Admin: true}, {Name: "lou"}},
repos: []string{"quarry"}}
sync, err := reconcileNotify(context.Background(), np, book)
if err != nil {
t.Fatalf("FAIL: %v", err)
}
fresh := np.setp["dmestas"]
if len(fresh) != 1 || len(fresh[0]) != len(notify.EmailFamilies()) {
t.Fatalf("FAIL: a fresh account got %+v, want one write covering every family", fresh)
}
for wf, cells := range fresh[0] {
if len(cells) != 1 || cells["email"] != false {
t.Errorf("FAIL: %s was written as %v, want only email:false", wf, cells)
}
}
chosen := np.setp["lou"]
if len(chosen) != 1 {
t.Fatalf("FAIL: an account with choices got %d writes, want 1", len(chosen))
}
if _, touched := chosen[0]["quarry-mention"]; touched {
t.Error("FAIL: an email somebody switched ON was overwritten")
}
if _, touched := chosen[0]["quarry-forum-reply"]; touched {
t.Error("FAIL: an email somebody switched OFF was rewritten")
}
if len(chosen[0]) != 2 {
t.Errorf("FAIL: lou's unanswered families were %v, want the two remaining", chosen[0])
}
if sync.Quieted != len(notify.EmailFamilies())+2 {
t.Errorf("FAIL: quieted %d", sync.Quieted)
}
// Second start: everything is answered, nothing is written.
np2 := newFakePlane()
np2.prefs = map[string]notify.SubscriberPreferences{}
for _, a := range book.accounts {
wf := map[string]map[string]bool{}
for _, f := range notify.EmailFamilies() {
wf[f] = map[string]bool{"email": false}
}
np2.prefs[a.Name] = notify.SubscriberPreferences{Workflows: wf}
}
sync, _ = reconcileNotify(context.Background(), np2, book)
if len(np2.setp) != 0 || sync.Quieted != 0 {
t.Errorf("FAIL: a second start wrote preferences again: %+v", np2.setp)
}
t.Log("ok: email is answered no for the unanswered, and only for them")
}
type fakeTrigger struct{ events []api.NotifyEvent }
func (f *fakeTrigger) Trigger(_ context.Context, e api.NotifyEvent) error {
f.events = append(f.events, e)
return nil
}
// An alert about a repository an admin cannot read carries its route
// and kind, never its page or its words (#318): per admin in the inbox,
// and in the one email to the admins group unless every admin reads it.
func TestAnAdminAlertHidesWhatTheAdminCannotRead(t *testing.T) {
att := &fakeAttention{}
book := fakeBook{accounts: []oltp.Account{{Name: "dmestas", Admin: true}, {Name: "root", Admin: true}}}
reads := func(account, repo string) bool { return repo != "vault" || account == "dmestas" }
e := errlog.Entry{Surface: "web", Repo: "vault", Path: "/r/vault/wiki/Secret-Plan",
Message: "render Secret-Plan failed", First: time.Now()}
alertAdmins(book, att, e, reads)
if got := att.sent["root"]; len(got) != 1 || strings.Contains(got[0].Summary, "Secret") {
t.Errorf("FAIL: root's inbox row is %+v", got)
}
if got := att.sent["dmestas"]; len(got) != 1 || !strings.Contains(got[0].Summary, "Secret") {
t.Errorf("FAIL: the admin who reads vault lost the words: %+v", got)
}
np := &fakeTrigger{}
alertAdminsTopic(context.Background(), np, book, e, reads, "https://forge.example")
if len(np.events) != 1 {
t.Fatalf("FAIL: %d group alerts, want 1", len(np.events))
}
if p := np.events[0].Payload; strings.Contains(fmt.Sprint(p["summary"], p["path"]), "Secret") {
t.Errorf("FAIL: the admins email carries vault's words: %v", p)
}
np = &fakeTrigger{}
alertAdminsTopic(context.Background(), np, book, e, func(string, string) bool { return true }, "")
if p := np.events[0].Payload; !strings.Contains(fmt.Sprint(p["summary"]), "Secret") {
t.Errorf("FAIL: an alert every admin may read lost its words: %v", p)
}
}