Skip to content
quarry Artifact [9f2b3d48e8]
Login

Artifact [9f2b3d48e8]

Artifact 9f2b3d48e82a4ea54153b10a63fe8c1dd565b562d1ff80c72eebe6121885d8a4:


package api

import (
	"context"
	"sort"
	"sync"
	"testing"
	"time"

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

// memChatNotify is chat_notify in memory, keyed repo\x00account.
type memChatNotify struct {
	mu    sync.Mutex
	modes map[string]string
}

func (m *memChatNotify) SetChatNotify(repo, account, mode string) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	if m.modes == nil {
		m.modes = map[string]string{}
	}
	if mode == oltp.ChatMentions {
		delete(m.modes, repo+"\x00"+account)
	} else {
		m.modes[repo+"\x00"+account] = mode
	}
	return nil
}

func (m *memChatNotify) ChatNotify(repo, account string) string {
	m.mu.Lock()
	defer m.mu.Unlock()
	if mode, ok := m.modes[repo+"\x00"+account]; ok {
		return mode
	}
	return oltp.ChatMentions
}

func (m *memChatNotify) ChatNotifyEvery(repo string) ([]string, error) {
	m.mu.Lock()
	defer m.mu.Unlock()
	var out []string
	for k, mode := range m.modes {
		if mode == oltp.ChatEvery && len(k) > len(repo) && k[:len(repo)+1] == repo+"\x00" {
			out = append(out, k[len(repo)+1:])
		}
	}
	sort.Strings(out)
	return out, nil
}

// A channel's bell decides who hears a message. Following every message
// tells a follower about each one, but not its author, not somebody it
// named (the mention already did), and not somebody who has lost the
// chat letter since following. Off silences even a mention.
func TestAChatChannelsBellDecidesWhoHears(t *testing.T) {
	fn := &fakeNotify{}
	store := &memChatNotify{}
	repos := testRepos(wikiIndex(t))
	mustRepo(t, repos, "test").Code = "c1"
	r := ops.NewRegistry()
	if err := RegisterAll(r, Deps{Version: "v", Repos: repos, Chat: &fakeChat{}, Notify: fn, ChatNotify: store,
		CapsFor: func(a, _ string) CapSet {
			if a == "eve" {
				return fakeCaps("o") // can read code, not chat
			}
			return fakeCaps(anyCapLetters)
		}}); err != nil {
		t.Fatal(err)
	}
	set := func(who, mode string) {
		t.Helper()
		ctx := ops.WithUser(context.Background(), who)
		if _, err := r.Invoke(ctx, "chat.notify.set", chatNotifySetIn{Repo: "test", Mode: mode}); err != nil {
			t.Fatal(err)
		}
	}
	set("dan", "all") // follows everything
	set("eve", "all") // followed, then lost the chat letter
	set("gus", "all") // follows, and is named: hears the mention only
	set("lou", "all") // the author: never told of their own message
	set("fay", "off") // off: not even the mention
	if got, _ := r.Invoke(ops.WithUser(context.Background(), "fay"), "chat.notify", chatNotifyIn{Repo: "test"}); got.(chatNotifyOut).Mode != "off" {
		t.Fatalf("FAIL: chat.notify for fay = %+v", got)
	}

	if _, err := r.Invoke(ops.WithUser(context.Background(), "lou"), "chat.post",
		chatPostIn{Repo: "test", Text: "release-233 is up, @fay @gus"}); err != nil {
		t.Fatal(err)
	}
	var evs []NotifyEvent
	for range 50 {
		if evs = fn.snapshot(); len(evs) >= 2 {
			break
		}
		time.Sleep(20 * time.Millisecond)
	}
	time.Sleep(60 * time.Millisecond) // anything extra would have landed by now
	got := map[string]string{}
	for _, e := range fn.snapshot() {
		got[e.To] += e.Workflow + " "
	}
	want := map[string]string{"dan": "quarry-chat ", "gus": "quarry-mention "}
	if len(got) != len(want) || got["dan"] != want["dan"] || got["gus"] != want["gus"] {
		t.Fatalf("FAIL: told %v, want %v", got, want)
	}
	t.Log("ok: a follower hears every message; the author, the named, the off and the unentitled do not hear it twice or at all")

	set("dan", "mentions")
	if list, _ := store.ChatNotifyEvery("test"); len(list) != 3 || list[0] != "eve" {
		t.Fatalf("FAIL: after dan went back to mentions, followers = %v", list)
	}
	t.Log("ok: going back to mentions stops following")
}