carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package console
2
3import (
4 "context"
5 "errors"
6 "net/url"
7 "testing"
8 "time"
9)
10
11func TestHasMailboxClassification(t *testing.T) {
12 fk := newFakeKeymail(t, "paul@keymail.dev")
13 k := fedClient(t, fk)
14 fk.lookupStatus["a@example.com"] = 200
15 fk.lookupStatus["b@example.com"] = 400
16 fk.lookupStatus["c@example.com"] = 404
17 fk.lookupStatus["d@example.com"] = 409
18 fk.lookupStatus["e@example.com"] = 500
19 fk.lookupStatus["f@example.com"] = 503
20 cases := []struct {
21 addr string
22 want bool
23 unreachable bool
24 }{
25 {"a@example.com", true, false},
26 {"b@example.com", false, false},
27 {"c@example.com", false, false},
28 {"d@example.com", false, false},
29 {"e@example.com", false, true},
30 {"f@example.com", false, true},
31 {"sam@keymail.dev", true, false}, // the fake's ordinary answer
32 }
33 for _, c := range cases {
34 got, err := k.HasMailbox(context.Background(), c.addr)
35 if got != c.want || errors.Is(err, ErrKeymailUnreachable) != c.unreachable || (err != nil) != c.unreachable {
36 t.Errorf("HasMailbox(%s) = %v, %v; want %v, unreachable=%v", c.addr, got, err, c.want, c.unreachable)
37 }
38 }
39 // A lookup that stalls past the caller's deadline is unreachable, not a no.
40 fk.lookupStall["slow@example.com"] = 3 * time.Second
41 fk.lookupStatus["slow@example.com"] = 200
42 ctx, cancel := context.WithTimeout(context.Background(), time.Second)
43 defer cancel()
44 start := time.Now()
45 got, err := k.HasMailbox(ctx, "slow@example.com")
46 if got || !errors.Is(err, ErrKeymailUnreachable) {
47 t.Errorf("stalled HasMailbox = %v, %v; want false, ErrKeymailUnreachable", got, err)
48 }
49 if d := time.Since(start); d > 2*time.Second {
50 t.Errorf("stalled HasMailbox took %v", d)
51 }
52}
53
54func TestKeymailHost(t *testing.T) {
55 for in, want := range map[string]string{
56 "https://keymail.dev": "keymail.dev",
57 "https://keymail.dev/": "keymail.dev",
58 "http://127.0.0.1:8123": "127.0.0.1",
59 "https://Keymail.Dev:8443": "keymail.dev",
60 } {
61 if got := NewKeymail(in, "https://console.example").Host(); got != want {
62 t.Errorf("Host(%q) = %q, want %q", in, got, want)
63 }
64 }
65}
66
67func TestSendFederatedClassifiesLookupErrors(t *testing.T) {
68 fk := newFakeKeymail(t, "paul@keymail.dev")
69 fk.lookupStatus["a@example.com"] = 409
70 fk.lookupStatus["b@example.com"] = 500
71 k := fedClient(t, fk)
72 if _, err := k.SendFederated(context.Background(), "a@example.com", "s", "b"); !errors.Is(err, ErrNoKeymailMailbox) || errors.Is(err, ErrKeymailUnreachable) {
73 t.Errorf("409: err = %v, want ErrNoKeymailMailbox only", err)
74 }
75 _, err := k.SendFederated(context.Background(), "b@example.com", "s", "b")
76 if !errors.Is(err, ErrKeymailUnreachable) || errors.Is(err, ErrNoKeymailMailbox) {
77 t.Errorf("500: err = %v, want ErrKeymailUnreachable only", err)
78 }
79 var ke *keymailError
80 if !errors.As(err, &ke) || ke.Status != 500 {
81 t.Errorf("500: err = %v, want the keymailError wrapped too", err)
82 }
83}
84
85// deliverFixture builds a Server whose federation leg is the real
86// SendFederated against the fake Keymail, with a recording mailer.
87func deliverFixture(t *testing.T) (*consoleFixture, *fakeConsoleMail) {
88 t.Helper()
89 fx := newConsoleFixture(t)
90 mailer := &fakeConsoleMail{ready: true}
91 fx.srv.consoleMail = mailer
92 return fx, mailer
93}
94
95func TestDeliverChain(t *testing.T) {
96 kmHost := func(fx *consoleFixture) string {
97 u, _ := url.Parse(fx.fk.srv.URL)
98 return u.Hostname()
99 }
100 cases := []struct {
101 name string
102 to func(fx *consoleFixture) string
103 setup func(fx *consoleFixture, to string)
104 noMailer bool
105 wantChannel deliveryChannel
106 wantErr func(error) bool
107 wantFed bool
108 wantMail bool
109 }{
110 {name: "no mailbox (404) goes to the mailer",
111 to: func(*consoleFixture) string { return "ghost@keymail.dev" },
112 setup: func(fx *consoleFixture, _ string) { fx.fk.noMailbox["ghost"] = true },
113 wantChannel: "email", wantMail: true},
114 {name: "foreign domain (400) goes to the mailer",
115 to: func(*consoleFixture) string { return "x@example.com" },
116 wantChannel: "email", wantMail: true},
117 {name: "unclaimed (409) goes to the mailer",
118 to: func(*consoleFixture) string { return "x@example.com" },
119 setup: func(fx *consoleFixture, to string) { fx.fk.lookupStatus[to] = 409 },
120 wantChannel: "email", wantMail: true},
121 {name: "federation delivers, mailer untouched",
122 to: func(*consoleFixture) string { return "sam@keymail.dev" },
123 wantChannel: "keymail", wantFed: true},
124 {name: "blocked stops",
125 to: func(*consoleFixture) string { return "sam@keymail.dev" },
126 setup: func(fx *consoleFixture, _ string) { fx.fk.blocked["console@"+OriginHost(fx.ts.URL)] = true },
127 wantErr: IsKeymailBlocked},
128 {name: "price too high stops",
129 to: func(*consoleFixture) string { return "sam@keymail.dev" },
130 setup: func(fx *consoleFixture, _ string) { fx.fk.powBits = maxFederationPowBits + 1 },
131 wantErr: func(err error) bool { return errors.Is(err, ErrFederationPriceTooHigh) }},
132 // The lookup said the mailbox exists, then the delivery itself failed
133 // (neither blocked nor price): still a Keymail address, never SES.
134 {name: "delivery 500 after a good lookup stops",
135 to: func(*consoleFixture) string { return "sam@keymail.dev" },
136 setup: func(fx *consoleFixture, _ string) { fx.fk.deliverStatus = 500 },
137 wantErr: func(err error) bool { var ke *keymailError; return errors.As(err, &ke) && ke.Status == 500 }},
138 {name: "delivery 500 for a custom domain with a mailbox stops",
139 to: func(*consoleFixture) string { return "x@example.com" },
140 setup: func(fx *consoleFixture, to string) {
141 fx.fk.lookupStatus[to] = 200
142 fx.fk.deliverStatus = 500
143 },
144 wantErr: func(err error) bool { var ke *keymailError; return errors.As(err, &ke) && ke.Status == 500 }},
145 {name: "lookup 500 for the keymail host stops",
146 to: func(fx *consoleFixture) string { return "x@" + kmHost(fx) },
147 setup: func(fx *consoleFixture, to string) { fx.fk.lookupStatus[to] = 500 },
148 wantErr: func(err error) bool { return errors.Is(err, ErrKeymailUnreachable) }},
149 {name: "lookup 500 for another domain goes to the mailer",
150 to: func(*consoleFixture) string { return "x@example.com" },
151 setup: func(fx *consoleFixture, to string) { fx.fk.lookupStatus[to] = 500 },
152 wantChannel: "email", wantMail: true},
153 {name: "no mailer is an error",
154 to: func(*consoleFixture) string { return "x@example.com" },
155 noMailer: true,
156 wantErr: func(err error) bool { return err != nil }},
157 }
158 for _, c := range cases {
159 t.Run(c.name, func(t *testing.T) {
160 fx, mailer := deliverFixture(t)
161 to := c.to(fx)
162 if c.setup != nil {
163 c.setup(fx, to)
164 }
165 if c.noMailer {
166 fx.srv.consoleMail = nil
167 }
168 ch, err := fx.srv.deliver(context.Background(), to, "subj", "the body", 5*time.Second)
169 if c.wantErr != nil {
170 if err == nil || !c.wantErr(err) {
171 t.Fatalf("err = %v, want the case's error", err)
172 }
173 if ch != "" {
174 t.Errorf("channel = %q on error", ch)
175 }
176 } else {
177 if err != nil {
178 t.Fatalf("err = %v", err)
179 }
180 if ch != c.wantChannel {
181 t.Errorf("channel = %q, want %q", ch, c.wantChannel)
182 }
183 }
184 if got := len(fx.fk.fedBodies()) > 0; got != c.wantFed {
185 t.Errorf("federated = %v, want %v", got, c.wantFed)
186 }
187 if got := len(mailer.sentCopy()) > 0; got != c.wantMail {
188 t.Errorf("mailed = %v, want %v", got, c.wantMail)
189 }
190 })
191 }
192}
193
194// The keymailShare bounds Keymail's slice of the budget: a stalled lookup for
195// a foreign domain leaves time for the mailer.
196func TestDeliverKeymailShareLeavesTimeForTheMailer(t *testing.T) {
197 fx, mailer := deliverFixture(t)
198 fx.fk.lookupStall["x@example.com"] = 3 * time.Second
199 ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
200 defer cancel()
201 start := time.Now()
202 ch, err := fx.srv.deliver(ctx, "x@example.com", "s", "b", 200*time.Millisecond)
203 if err != nil || ch != "email" {
204 t.Fatalf("deliver = %q, %v; want email", ch, err)
205 }
206 if d := time.Since(start); d > time.Second {
207 t.Errorf("deliver took %v", d)
208 }
209 if len(mailer.sentCopy()) != 1 {
210 t.Errorf("mailer calls = %v", mailer.sentCopy())
211 }
212}
213