internal/store/emails_test.go
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}