e2e/notify_test.go

1ed9fb9399b21da8e6cf45be8389792aac82d5bc
gitbay/e2e/notify_test.go history · blame · raw

166 lines · 5865 bytes

  1package e2e
  2
  3import (
  4	"fmt"
  5	"net"
  6	"os"
  7	"os/exec"
  8	"strings"
  9	"testing"
 10	"time"
 11)
 12
 13// mailTo returns captured messages addressed to one recipient.
 14func (f *fakeSMTP) mailTo(recipient string) []string {
 15	f.mu.Lock()
 16	defer f.mu.Unlock()
 17	var out []string
 18	for _, m := range f.mail {
 19		if strings.Contains(m, "To: "+recipient) {
 20			out = append(out, m)
 21		}
 22	}
 23	return out
 24}
 25
 26func (f *fakeSMTP) waitFor(t *testing.T, recipient, substr string) string {
 27	t.Helper()
 28	deadline := time.Now().Add(15 * time.Second)
 29	for time.Now().Before(deadline) {
 30		for _, m := range f.mailTo(recipient) {
 31			if strings.Contains(m, substr) {
 32				return m
 33			}
 34		}
 35		time.Sleep(100 * time.Millisecond)
 36	}
 37	t.Fatalf("no mail to %s containing %q", recipient, substr)
 38	return ""
 39}
 40
 41func TestActivityNotifications(t *testing.T) {
 42	t.Parallel()
 43	smtp := startFakeSMTP(t)
 44	inst := startInstanceWith(t, fmt.Sprintf(
 45		"[mail]\nsmtp_host = %q\nfrom = \"noreply@gitbay.test\"\n", smtp.addr))
 46	// Fast retries for the mailer loop.
 47	inst.proc.Process.Kill()
 48	inst.proc.Wait()
 49	inst.proc = exec.Command(inst.gitbayd, "--config", inst.config, "serve")
 50	inst.proc.Env = append(os.Environ(), "GITBAY_WEBHOOK_RETRY_BASE=500ms")
 51	inst.proc.Stderr = os.Stderr
 52	if err := inst.proc.Start(); err != nil {
 53		t.Fatal(err)
 54	}
 55	t.Cleanup(func() { inst.proc.Process.Kill(); inst.proc.Wait() })
 56	deadline := time.Now().Add(10 * time.Second)
 57	for {
 58		conn, err := net.DialTimeout("tcp", fmt.Sprintf("127.0.0.1:%d", inst.port), 200*time.Millisecond)
 59		if err == nil {
 60			conn.Close()
 61			break
 62		}
 63		if time.Now().After(deadline) {
 64			t.Fatal("daemon did not restart")
 65		}
 66		time.Sleep(50 * time.Millisecond)
 67	}
 68
 69	aliceKey := inst.newKey(t, "alice")
 70	bobKey := inst.newKey(t, "bob")
 71	eveKey := inst.newKey(t, "eve")
 72	inst.admin(t, "admin", "user", "create", "alice",
 73		"--key", aliceKey+".pub", "--email", "alice@example.test", "--verified")
 74	inst.admin(t, "admin", "user", "create", "bob",
 75		"--key", bobKey+".pub", "--email", "bob@example.test", "--verified")
 76	// eve has NO verified email: she must be skipped silently.
 77	inst.admin(t, "admin", "user", "create", "eve",
 78		"--key", eveKey+".pub", "--email", "eve@example.test")
 79
 80	if _, errOut, code := inst.ssh(t, aliceKey, "", "repo", "create", "alice/app"); code != 0 {
 81		t.Fatalf("repo create: %s", errOut)
 82	}
 83
 84	// Bob opens an issue: the repo owner (alice) is notified with subject,
 85	// excerpt, and link; bob (the actor) is not.
 86	if _, errOut, code := inst.ssh(t, bobKey, "",
 87		"issue", "create", "alice/app", "--title", "'it leaks'", "--body", "'memory climbs forever'"); code != 0 {
 88		t.Fatalf("issue create: %s", errOut)
 89	}
 90	m := smtp.waitFor(t, "alice@example.test", "#1")
 91	if !strings.Contains(m, "[alice/app] #1: it leaks") ||
 92		!strings.Contains(m, "bob opened issue #1") ||
 93		!strings.Contains(m, "memory climbs forever") ||
 94		!strings.Contains(m, "/alice/app/issues/1") {
 95		t.Fatalf("issue-open mail:\n%s", m)
 96	}
 97	if n := len(smtp.mailTo("bob@example.test")); n != 0 {
 98		t.Fatalf("actor notified about own action (%d mails)", n)
 99	}
100
101	// Alice comments: bob (author/participant) is notified; alice is not.
102	if _, _, code := inst.ssh(t, aliceKey, "", "issue", "comment", "alice/app", "1", "--message", "'on it'"); code != 0 {
103		t.Fatal("comment failed")
104	}
105	m = smtp.waitFor(t, "bob@example.test", "commented")
106	if !strings.Contains(m, "alice commented on #1") || !strings.Contains(m, "on it") {
107		t.Fatalf("comment mail:\n%s", m)
108	}
109
110	// Eve comments (unverified email, still allowed to act): both alice
111	// and bob get mail; eve never receives any.
112	if _, _, code := inst.ssh(t, eveKey, "", "issue", "comment", "alice/app", "1", "--message", "'same here'"); code != 0 {
113		t.Fatal("eve comment failed")
114	}
115	smtp.waitFor(t, "alice@example.test", "same here")
116	smtp.waitFor(t, "bob@example.test", "same here")
117
118	// Close notifies participants.
119	if _, _, code := inst.ssh(t, aliceKey, "", "issue", "close", "alice/app", "1"); code != 0 {
120		t.Fatal("close failed")
121	}
122	m = smtp.waitFor(t, "bob@example.test", "closed #1")
123	if !strings.Contains(m, "alice closed #1") {
124		t.Fatalf("close mail:\n%s", m)
125	}
126	if n := len(smtp.mailTo("eve@example.test")); n != 0 {
127		t.Fatalf("unverified recipient got mail (%d)", n)
128	}
129
130	// MR flow: bob opens (alice notified), alice reviews (bob notified),
131	// alice merges (bob notified).
132	if _, _, code := inst.ssh(t, aliceKey, "", "repo", "access", "grant", "alice/app", "bob", "write"); code != 0 {
133		t.Fatal("grant failed")
134	}
135	work := t.TempDir()
136	bobEnv := inst.gitEnv(bobKey)
137	mustGit(t, work, bobEnv, "clone", inst.sshURL("alice/app"), "w")
138	dir := work + "/w"
139	os.WriteFile(dir+"/f.txt", []byte("x\n"), 0o644)
140	mustGit(t, dir, bobEnv, "checkout", "-q", "-b", "main")
141	mustGit(t, dir, bobEnv, "add", ".")
142	mustGit(t, dir, bobEnv, "commit", "-q", "-m", "base")
143	mustGit(t, dir, bobEnv, "push", "-q", "origin", "main")
144	mustGit(t, dir, bobEnv, "checkout", "-q", "-b", "feat")
145	mustGit(t, dir, bobEnv, "commit", "-q", "--allow-empty", "-m", "work")
146	mustGit(t, dir, bobEnv, "push", "-q", "origin", "feat")
147	if _, errOut, code := inst.ssh(t, bobKey, "", "mr", "create", "alice/app",
148		"--source", "feat", "--target", "main", "--title", "'ship it'"); code != 0 {
149		t.Fatalf("mr create: %s", errOut)
150	}
151	m = smtp.waitFor(t, "alice@example.test", "!1")
152	if !strings.Contains(m, "bob opened merge request !1") {
153		t.Fatalf("mr-open mail:\n%s", m)
154	}
155	if _, _, code := inst.ssh(t, aliceKey, "", "mr", "review", "alice/app", "1", "--approve"); code != 0 {
156		t.Fatal("review failed")
157	}
158	smtp.waitFor(t, "bob@example.test", "reviewed !1: approve")
159	if _, errOut, code := inst.ssh(t, aliceKey, "", "mr", "merge", "alice/app", "1"); code != 0 {
160		t.Fatalf("merge: %s", errOut)
161	}
162	m = smtp.waitFor(t, "bob@example.test", "merged !1")
163	if !strings.Contains(m, "alice merged !1 into main") {
164		t.Fatalf("merge mail:\n%s", m)
165	}
166}