Skip to content
quarry Artifact [c8e49421c6]
Login

Artifact [c8e49421c6]

Artifact c8e49421c663171ff40fec2aa8f1a6509957eff59d8b7aacd10627eca71fae23:


package api

import (
	"strings"
	"testing"

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

// stubTeamRoster answers TeamMembers only; the embedded nil interface
// panics on anything else, so a test cannot lean on it by accident.
type stubTeamRoster struct {
	TeamStore
	members []string
}

func (s stubTeamRoster) TeamMembers(string) ([]oltp.TeamMember, error) {
	out := make([]oltp.TeamMember, len(s.members))
	for i, m := range s.members {
		out[i] = oltp.TeamMember{Account: m}
	}
	return out, nil
}

// stubForumSubs is the three ForumStore calls forumFanout makes.
type stubForumSubs struct{ ForumStore }

func (stubForumSubs) AutoSubscribe(_, _, _ string) error             { return nil }
func (stubForumSubs) ForumSubscribers(_, _ string) ([]string, error) { return nil, nil }
func (stubForumSubs) RecordMention(_, _, _, _ string) error          { return nil }

// mentionLinks fires each of the three mention paths once at dan and
// returns the payload link the notify seam saw, keyed by path.
func mentionLinks(t *testing.T, origin string) map[string]string {
	t.Helper()
	paths := map[string]func(d Deps){
		"chat": func(d Deps) { d.chatMentionAt("alpha", "hi @dan", "eve", 7) },
		"team": func(d Deps) { d.teamMentionAt("crew", "hi @dan", "eve", 7) },
		"forum": func(d Deps) {
			forumFanout(d, "alpha", "abcdef0123456789abcdef", "hash0123456789ab", "eve", "t", "hi @dan", "")
		},
	}
	got := map[string]string{}
	for name, fire := range paths {
		fn := &fakeNotify{}
		d := Deps{Origin: origin, Notify: fn, Forum: stubForumSubs{},
			Teams: stubTeamRoster{members: []string{"dan", "eve"}}}
		fire(d)
		evs := waitNotify(fn, 1)
		if len(evs) != 1 {
			t.Fatalf("%s: %d events, want 1", name, len(evs))
		}
		got[name], _ = evs[0].Payload["link"].(string)
	}
	return got
}

func TestMentionLinksAreAbsoluteWhenOriginSet(t *testing.T) {
	const origin = "https://forge.example"
	for name, link := range mentionLinks(t, origin) {
		if !strings.HasPrefix(link, origin+"/") {
			t.Errorf("%s: link %q does not start with the origin", name, link)
		}
	}
}

func TestMentionLinksStayRelativeWithoutOrigin(t *testing.T) {
	for name, link := range mentionLinks(t, "") {
		if !strings.HasPrefix(link, "/") {
			t.Errorf("%s: link %q should stay site-relative", name, link)
		}
	}
}

func TestFireNotifyLeavesAbsoluteAndMissingLinksAlone(t *testing.T) {
	fn := &fakeNotify{}
	d := Deps{Origin: "https://forge.example", Notify: fn}
	const abs = "https://other.example/x"
	d.fireNotify(NotifyEvent{Workflow: "w", To: "a", Payload: map[string]any{"link": abs}})
	d.fireNotify(NotifyEvent{Workflow: "w", To: "b", Payload: map[string]any{"repo": "r"}})
	evs := waitNotify(fn, 2)
	if len(evs) != 2 {
		t.Fatalf("%d events, want 2", len(evs))
	}
	for _, e := range evs {
		switch e.To {
		case "a":
			if e.Payload["link"] != abs {
				t.Errorf("absolute link rewritten to %v", e.Payload["link"])
			}
		case "b":
			if _, ok := e.Payload["link"]; ok {
				t.Errorf("a link appeared from nowhere: %v", e.Payload["link"])
			}
		}
	}
}

// The caller's payload map is shared with the attention publish; the
// rewrite must not reach back into it.
func TestFireNotifyDoesNotMutateCallerPayload(t *testing.T) {
	fn := &fakeNotify{}
	d := Deps{Origin: "https://forge.example", Notify: fn}
	p := map[string]any{"link": "/r/a/chat/1"}
	d.fireNotify(NotifyEvent{Workflow: "w", To: "a", Payload: p})
	waitNotify(fn, 1)
	if p["link"] != "/r/a/chat/1" {
		t.Fatalf("caller payload mutated: %v", p["link"])
	}
}