e2e/inbox_test.go

v1.20.0
gitbay/e2e/inbox_test.go history · blame · raw

144 lines · 5117 bytes

  1package e2e
  2
  3import (
  4	"encoding/json"
  5	"strconv"
  6	"strings"
  7	"testing"
  8)
  9
 10// notices reads a user's inbox. The instance under test has no SMTP, so
 11// this also asserts the inbox is filed independently of mail.
 12func notices(t *testing.T, inst *instance, key string, args ...string) []struct {
 13	ID      int64  `json:"id"`
 14	Repo    string `json:"repo"`
 15	Kind    string `json:"kind"`
 16	Actor   string `json:"actor"`
 17	Summary string `json:"summary"`
 18	Path    string `json:"path"`
 19	ReadAt  string `json:"read_at"`
 20} {
 21	t.Helper()
 22	out, errOut, code := inst.ssh(t, key, "", append([]string{"notifications", "list"}, append(args, "--json")...)...)
 23	if code != 0 {
 24		t.Fatalf("notifications list: %s", errOut)
 25	}
 26	var env struct {
 27		Data []struct {
 28			ID      int64  `json:"id"`
 29			Repo    string `json:"repo"`
 30			Kind    string `json:"kind"`
 31			Actor   string `json:"actor"`
 32			Summary string `json:"summary"`
 33			Path    string `json:"path"`
 34			ReadAt  string `json:"read_at"`
 35		} `json:"data"`
 36	}
 37	if err := json.Unmarshal([]byte(out), &env); err != nil {
 38		t.Fatalf("not JSON: %v\n%s", err, out)
 39	}
 40	return env.Data
 41}
 42
 43func TestNotificationInbox(t *testing.T) {
 44	inst := startInstance(t)
 45
 46	aliceKey := inst.newKey(t, "alice")
 47	bobKey := inst.newKey(t, "bob")
 48	eveKey := inst.newKey(t, "eve")
 49	inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
 50	inst.admin(t, "admin", "user", "create", "bob", "--key", bobKey+".pub")
 51	inst.admin(t, "admin", "user", "create", "eve", "--key", eveKey+".pub")
 52
 53	if _, errOut, code := inst.ssh(t, aliceKey, "", "repo", "create", "alice/app"); code != 0 {
 54		t.Fatalf("repo create: %s", errOut)
 55	}
 56
 57	// Bob opens an issue. The owner hears about it; the actor does not.
 58	if _, errOut, code := inst.ssh(t, bobKey, "", "issue", "create", "alice/app",
 59		"--title", "'it leaks'", "--body", "'memory climbs'"); code != 0 {
 60		t.Fatalf("issue create: %s", errOut)
 61	}
 62	got := notices(t, inst, aliceKey)
 63	if len(got) != 1 || got[0].Actor != "bob" || got[0].Summary != "opened issue #1" ||
 64		got[0].Repo != "alice/app" || got[0].Kind != "issue" || got[0].Path != "alice/app/issues/1" {
 65		t.Fatalf("alice inbox = %+v", got)
 66	}
 67	if n := notices(t, inst, bobKey); len(n) != 0 {
 68		t.Fatalf("actor notified about own action: %+v", n)
 69	}
 70
 71	// Eve neither owns the repository nor took part in the issue, so
 72	// nothing has reached her.
 73	if n := notices(t, inst, eveKey); len(n) != 0 {
 74		t.Fatalf("uninvolved user notified: %+v", n)
 75	}
 76
 77	// Watching widens the recipients to her.
 78	if _, errOut, code := inst.ssh(t, eveKey, "", "repo", "watch", "alice/app"); code != 0 {
 79		t.Fatalf("repo watch: %s", errOut)
 80	}
 81	if _, _, code := inst.ssh(t, aliceKey, "", "issue", "comment", "alice/app", "1", "--message", "'on it'"); code != 0 {
 82		t.Fatal("alice comment failed")
 83	}
 84	eve := notices(t, inst, eveKey)
 85	if len(eve) != 1 || eve[0].Summary != "commented on #1" || eve[0].Actor != "alice" {
 86		t.Fatalf("watcher inbox = %+v", eve)
 87	}
 88	if n := len(notices(t, inst, bobKey)); n != 1 {
 89		t.Fatalf("issue author inbox = %d rows, want 1", n)
 90	}
 91
 92	// Muting beats being a participant: bob wrote the issue and still
 93	// hears nothing more, while the watcher does.
 94	if _, errOut, code := inst.ssh(t, bobKey, "", "repo", "mute", "alice/app"); code != 0 {
 95		t.Fatalf("repo mute: %s", errOut)
 96	}
 97	if _, _, code := inst.ssh(t, aliceKey, "", "issue", "comment", "alice/app", "1", "--message", "'more'"); code != 0 {
 98		t.Fatal("alice comment failed")
 99	}
100	if n := len(notices(t, inst, bobKey)); n != 1 {
101		t.Fatalf("muted participant inbox = %d rows, want 1", n)
102	}
103	if n := len(notices(t, inst, eveKey)); n != 2 {
104		t.Fatalf("watcher inbox = %d rows, want 2", n)
105	}
106
107	// Reading one drops it from the default list but not from --all.
108	eve = notices(t, inst, eveKey)
109	if _, errOut, code := inst.ssh(t, eveKey, "", "notifications", "read", strconv.FormatInt(eve[0].ID, 10)); code != 0 {
110		t.Fatalf("notifications read: %s", errOut)
111	}
112	if n := len(notices(t, inst, eveKey)); n != 1 {
113		t.Fatalf("unread list = %d rows, want 1", n)
114	}
115	all := notices(t, inst, eveKey, "--all")
116	if len(all) != 2 || all[0].ReadAt == "" {
117		t.Fatalf("--all = %+v", all)
118	}
119
120	// The dashboard carries the badge count, and --all sweeps it to zero.
121	out, _, code := inst.ssh(t, eveKey, "", "dashboard", "--json")
122	if code != 0 || !strings.Contains(out, `"unread":1`) {
123		t.Fatalf("dashboard unread: %s", out)
124	}
125	if _, errOut, code := inst.ssh(t, eveKey, "", "notifications", "read", "--all"); code != 0 {
126		t.Fatalf("read --all: %s", errOut)
127	}
128	if n := len(notices(t, inst, eveKey)); n != 0 {
129		t.Fatalf("unread after sweep = %d", n)
130	}
131
132	// One account cannot mark another's notifications read.
133	alice := notices(t, inst, aliceKey)
134	if len(alice) == 0 {
135		t.Fatal("alice has no notices to test with")
136	}
137	if out, _, code := inst.ssh(t, eveKey, "", "notifications", "read", strconv.FormatInt(alice[0].ID, 10), "--json"); code != 0 ||
138		!strings.Contains(out, `"read":0`) {
139		t.Fatalf("cross-account read: %s", out)
140	}
141	if n := len(notices(t, inst, aliceKey)); n != len(alice) {
142		t.Fatal("another account cleared alice's inbox")
143	}
144}