e2e/notify_test.go
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}