e2e/inbox_test.go

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

145 lines · 5131 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	t.Parallel()
 45	inst := startInstance(t)
 46
 47	aliceKey := inst.newKey(t, "alice")
 48	bobKey := inst.newKey(t, "bob")
 49	eveKey := inst.newKey(t, "eve")
 50	inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
 51	inst.admin(t, "admin", "user", "create", "bob", "--key", bobKey+".pub")
 52	inst.admin(t, "admin", "user", "create", "eve", "--key", eveKey+".pub")
 53
 54	if _, errOut, code := inst.ssh(t, aliceKey, "", "repo", "create", "alice/app"); code != 0 {
 55		t.Fatalf("repo create: %s", errOut)
 56	}
 57
 58	// Bob opens an issue. The owner hears about it; the actor does not.
 59	if _, errOut, code := inst.ssh(t, bobKey, "", "issue", "create", "alice/app",
 60		"--title", "'it leaks'", "--body", "'memory climbs'"); code != 0 {
 61		t.Fatalf("issue create: %s", errOut)
 62	}
 63	got := notices(t, inst, aliceKey)
 64	if len(got) != 1 || got[0].Actor != "bob" || got[0].Summary != "opened issue #1" ||
 65		got[0].Repo != "alice/app" || got[0].Kind != "issue" || got[0].Path != "alice/app/issues/1" {
 66		t.Fatalf("alice inbox = %+v", got)
 67	}
 68	if n := notices(t, inst, bobKey); len(n) != 0 {
 69		t.Fatalf("actor notified about own action: %+v", n)
 70	}
 71
 72	// Eve neither owns the repository nor took part in the issue, so
 73	// nothing has reached her.
 74	if n := notices(t, inst, eveKey); len(n) != 0 {
 75		t.Fatalf("uninvolved user notified: %+v", n)
 76	}
 77
 78	// Watching widens the recipients to her.
 79	if _, errOut, code := inst.ssh(t, eveKey, "", "repo", "watch", "alice/app"); code != 0 {
 80		t.Fatalf("repo watch: %s", errOut)
 81	}
 82	if _, _, code := inst.ssh(t, aliceKey, "", "issue", "comment", "alice/app", "1", "--message", "'on it'"); code != 0 {
 83		t.Fatal("alice comment failed")
 84	}
 85	eve := notices(t, inst, eveKey)
 86	if len(eve) != 1 || eve[0].Summary != "commented on #1" || eve[0].Actor != "alice" {
 87		t.Fatalf("watcher inbox = %+v", eve)
 88	}
 89	if n := len(notices(t, inst, bobKey)); n != 1 {
 90		t.Fatalf("issue author inbox = %d rows, want 1", n)
 91	}
 92
 93	// Muting beats being a participant: bob wrote the issue and still
 94	// hears nothing more, while the watcher does.
 95	if _, errOut, code := inst.ssh(t, bobKey, "", "repo", "mute", "alice/app"); code != 0 {
 96		t.Fatalf("repo mute: %s", errOut)
 97	}
 98	if _, _, code := inst.ssh(t, aliceKey, "", "issue", "comment", "alice/app", "1", "--message", "'more'"); code != 0 {
 99		t.Fatal("alice comment failed")
100	}
101	if n := len(notices(t, inst, bobKey)); n != 1 {
102		t.Fatalf("muted participant inbox = %d rows, want 1", n)
103	}
104	if n := len(notices(t, inst, eveKey)); n != 2 {
105		t.Fatalf("watcher inbox = %d rows, want 2", n)
106	}
107
108	// Reading one drops it from the default list but not from --all.
109	eve = notices(t, inst, eveKey)
110	if _, errOut, code := inst.ssh(t, eveKey, "", "notifications", "read", strconv.FormatInt(eve[0].ID, 10)); code != 0 {
111		t.Fatalf("notifications read: %s", errOut)
112	}
113	if n := len(notices(t, inst, eveKey)); n != 1 {
114		t.Fatalf("unread list = %d rows, want 1", n)
115	}
116	all := notices(t, inst, eveKey, "--all")
117	if len(all) != 2 || all[0].ReadAt == "" {
118		t.Fatalf("--all = %+v", all)
119	}
120
121	// The dashboard carries the badge count, and --all sweeps it to zero.
122	out, _, code := inst.ssh(t, eveKey, "", "dashboard", "--json")
123	if code != 0 || !strings.Contains(out, `"unread":1`) {
124		t.Fatalf("dashboard unread: %s", out)
125	}
126	if _, errOut, code := inst.ssh(t, eveKey, "", "notifications", "read", "--all"); code != 0 {
127		t.Fatalf("read --all: %s", errOut)
128	}
129	if n := len(notices(t, inst, eveKey)); n != 0 {
130		t.Fatalf("unread after sweep = %d", n)
131	}
132
133	// One account cannot mark another's notifications read.
134	alice := notices(t, inst, aliceKey)
135	if len(alice) == 0 {
136		t.Fatal("alice has no notices to test with")
137	}
138	if out, _, code := inst.ssh(t, eveKey, "", "notifications", "read", strconv.FormatInt(alice[0].ID, 10), "--json"); code != 0 ||
139		!strings.Contains(out, `"read":0`) {
140		t.Fatalf("cross-account read: %s", out)
141	}
142	if n := len(notices(t, inst, aliceKey)); n != len(alice) {
143		t.Fatal("another account cleared alice's inbox")
144	}
145}