internal/store/emails_test.go

f3f7422f62a73bb798f9cf9c5f4c344212a27f60
gitbay/internal/store/emails_test.go history · blame · raw

187 lines · 5309 bytes

  1package store
  2
  3import (
  4	"errors"
  5	"testing"
  6	"time"
  7)
  8
  9func emailFixture(t *testing.T) (*Store, int64) {
 10	t.Helper()
 11	s := open(t)
 12	if err := s.MigrateUp(); err != nil {
 13		t.Fatal(err)
 14	}
 15	uid, err := s.CreateUser("gus", false)
 16	if err != nil {
 17		t.Fatal(err)
 18	}
 19	if err := s.AddEmail(uid, "gus@primary.test", "smtp", true); err != nil {
 20		t.Fatal(err)
 21	}
 22	return s, uid
 23}
 24
 25func hasEmail(t *testing.T, s *Store, uid int64, address string) bool {
 26	t.Helper()
 27	list, err := s.ListEmails(uid)
 28	if err != nil {
 29		t.Fatal(err)
 30	}
 31	for _, e := range list {
 32		if e.Address == address {
 33			return true
 34		}
 35	}
 36	return false
 37}
 38
 39func epoch(t *testing.T, s *Store) int64 {
 40	t.Helper()
 41	v, err := s.KeyEpoch()
 42	if err != nil {
 43		t.Fatal(err)
 44	}
 45	return v
 46}
 47
 48// Removing an unverified address takes its pending verification code
 49// with it, and does not touch the key epoch: an unverified address was
 50// never a trust input.
 51func TestRemoveEmailUnverified(t *testing.T) {
 52	s, uid := emailFixture(t)
 53	if err := s.AddEmail(uid, "typo@example.test", "", false); err != nil {
 54		t.Fatal(err)
 55	}
 56	if err := s.CreateEmailToken(uid, "typo@example.test", "hash1", time.Hour); err != nil {
 57		t.Fatal(err)
 58	}
 59	before := epoch(t, s)
 60	if err := s.RemoveEmail(uid, "typo@example.test"); err != nil {
 61		t.Fatal(err)
 62	}
 63	if hasEmail(t, s, uid, "typo@example.test") {
 64		t.Fatal("address still listed after removal")
 65	}
 66	if _, err := s.ConsumeEmailToken(uid, "hash1"); !errors.Is(err, ErrNotFound) {
 67		t.Fatalf("verification code survived the address: %v", err)
 68	}
 69	if got := epoch(t, s); got != before {
 70		t.Fatalf("key epoch moved %d -> %d for an unverified address", before, got)
 71	}
 72}
 73
 74// A verified address is a trust input for signature states, so removing
 75// one invalidates the cache.
 76func TestRemoveEmailVerifiedBumpsKeyEpoch(t *testing.T) {
 77	s, uid := emailFixture(t)
 78	if err := s.AddEmail(uid, "old@example.test", "smtp", false); err != nil {
 79		t.Fatal(err)
 80	}
 81	before := epoch(t, s)
 82	if err := s.RemoveEmail(uid, "old@example.test"); err != nil {
 83		t.Fatal(err)
 84	}
 85	if got := epoch(t, s); got != before+1 {
 86		t.Fatalf("key epoch %d -> %d, want +1", before, got)
 87	}
 88}
 89
 90func TestRemoveEmailRefusesPrimary(t *testing.T) {
 91	s, uid := emailFixture(t)
 92	if err := s.AddEmail(uid, "other@example.test", "smtp", false); err != nil {
 93		t.Fatal(err)
 94	}
 95	if err := s.RemoveEmail(uid, "gus@primary.test"); !errors.Is(err, ErrPrimaryEmail) {
 96		t.Fatalf("removing the primary: %v", err)
 97	}
 98	if !hasEmail(t, s, uid, "gus@primary.test") {
 99		t.Fatal("primary removed despite refusal")
100	}
101}
102
103// Activation, login links and commit identity all resolve through
104// verified addresses; the last one stays even when it is not primary.
105func TestRemoveEmailRefusesLastVerified(t *testing.T) {
106	s := open(t)
107	if err := s.MigrateUp(); err != nil {
108		t.Fatal(err)
109	}
110	uid, err := s.CreateUser("gus", false)
111	if err != nil {
112		t.Fatal(err)
113	}
114	if err := s.AddEmail(uid, "gus@primary.test", "", true); err != nil {
115		t.Fatal(err)
116	}
117	if err := s.AddEmail(uid, "only@example.test", "smtp", false); err != nil {
118		t.Fatal(err)
119	}
120	if err := s.RemoveEmail(uid, "only@example.test"); !errors.Is(err, ErrLastVerifiedEmail) {
121		t.Fatalf("removing the only verified address: %v", err)
122	}
123	if !hasEmail(t, s, uid, "only@example.test") {
124		t.Fatal("last verified address removed despite refusal")
125	}
126}
127
128// An address on another account, or on none, is not found: the
129// uniqueness of addresses must not let one account act on another's.
130func TestRemoveEmailNotFound(t *testing.T) {
131	s, uid := emailFixture(t)
132	other, err := s.CreateUser("ada", false)
133	if err != nil {
134		t.Fatal(err)
135	}
136	if err := s.AddEmail(other, "ada@example.test", "", true); err != nil {
137		t.Fatal(err)
138	}
139	if err := s.RemoveEmail(uid, "ada@example.test"); !errors.Is(err, ErrNotFound) {
140		t.Fatalf("another account's address: %v", err)
141	}
142	if !hasEmail(t, s, other, "ada@example.test") {
143		t.Fatal("another account's address was removed")
144	}
145	if err := s.RemoveEmail(uid, "nobody@example.test"); !errors.Is(err, ErrNotFound) {
146		t.Fatalf("absent address: %v", err)
147	}
148}
149
150func TestSetPrimaryEmail(t *testing.T) {
151	s, uid := emailFixture(t)
152	if err := s.AddEmail(uid, "new@example.test", "smtp", false); err != nil {
153		t.Fatal(err)
154	}
155	if err := s.AddEmail(uid, "pending@example.test", "", false); err != nil {
156		t.Fatal(err)
157	}
158	if err := s.SetPrimaryEmail(uid, "pending@example.test"); !errors.Is(err, ErrUnverifiedEmail) {
159		t.Fatalf("unverified address as primary: %v", err)
160	}
161	if err := s.SetPrimaryEmail(uid, "nobody@example.test"); !errors.Is(err, ErrNotFound) {
162		t.Fatalf("absent address as primary: %v", err)
163	}
164	if err := s.SetPrimaryEmail(uid, "new@example.test"); err != nil {
165		t.Fatal(err)
166	}
167	list, err := s.ListEmails(uid)
168	if err != nil {
169		t.Fatal(err)
170	}
171	var primaries []string
172	for _, e := range list {
173		if e.Primary {
174			primaries = append(primaries, e.Address)
175		}
176	}
177	if len(primaries) != 1 || primaries[0] != "new@example.test" {
178		t.Fatalf("primaries after change: %v", primaries)
179	}
180	if addr, _ := s.PrimaryVerifiedEmail(uid); addr != "new@example.test" {
181		t.Fatalf("PrimaryVerifiedEmail after change: %q", addr)
182	}
183	// The old primary can go now.
184	if err := s.RemoveEmail(uid, "gus@primary.test"); err != nil {
185		t.Fatalf("removing the former primary: %v", err)
186	}
187}