oficina / meet Public

Clone
git clone https://amadan.net/oficina/meet

Plain git — no account needed to clone.

Download

Download this file

1package calls
2
3import (
4 "bytes"
5 "context"
6 "fmt"
7 "log/slog"
8 "strings"
9 "testing"
10
11 "amadan.net/rastrillo/rastrillo/mail"
12)
13
14type failingSender struct{ err error }
15
16func (f failingSender) Send(ctx context.Context, to, subject, body string) error { return f.err }
17
18// smtpish is a distinct type so the log line's "type" field is worth
19// something: it names what failed without naming who it failed for.
20type smtpish struct{ msg string }
21
22func (e smtpish) Error() string { return e.msg }
23
24func capture() (*slog.Logger, *bytes.Buffer) {
25 var buf bytes.Buffer
26 return slog.New(slog.NewTextHandler(&buf, nil)), &buf
27}
28
29func TestMailErrorsCarryNoAddress(t *testing.T) {
30 const address = "Guest@Example.com"
31 inner := failingSender{err: fmt.Errorf("rastrillo/mail: send to %s: %w",
32 address, smtpish{msg: "550 no mailbox for guest@example.com"})}
33 logger, buf := capture()
34
35 err := RedactMailErrors(inner, logger).Send(context.Background(), address, "s", "b")
36 if err == nil {
37 t.Fatal("a failed send answered nil")
38 }
39 if err.Error() != "calls: mail delivery failed" {
40 t.Fatalf("returned error is %q; it must be derived from nothing", err)
41 }
42 if strings.Contains(strings.ToLower(err.Error()), "guest") {
43 t.Fatalf("the returned error carries the address: %q", err)
44 }
45 log := buf.String()
46 if strings.Contains(strings.ToLower(log), "guest") {
47 t.Fatalf("the log line carries the address:\n%s", log)
48 }
49 if !strings.Contains(log, "calls.smtpish") {
50 t.Fatalf("the log line does not name the error's type:\n%s", log)
51 }
52 if !strings.Contains(log, "address_seen=true") {
53 t.Fatalf("the log line does not say the address was in the message:\n%s", log)
54 }
55}
56
57// The rune trap the first version of this file walked into (codex round
58// 2, C2-1): it replaced the address in the message with "[address]" by
59// finding an offset in a strings.ToLower copy and slicing the original
60// at that offset. strings.ToLower can change a string's byte length —
61// KELVIN SIGN (U+212A, three bytes) folds to one-byte "k"; capital
62// dotted I (U+0130, two bytes) folds to two-byte "i̇" — so an offset
63// found in the lowered copy does not address the same bytes in the
64// original, and applying it could leave a suffix of the address in the
65// "redacted" text or cut a rune in half.
66//
67// This version logs nothing derived from the message — only a bool, via
68// strings.Contains over two independently-lowered strings, no slicing —
69// so there is nothing left to desynchronise. This test is the guard
70// that keeps it that way: two addresses carrying both trap runes, inside
71// one error message that also contains a run of ordinary ASCII "KKK"s
72// (an SMTP server rejecting a batch tends to shout), asserting the log
73// holds no "@" and no fragment of either address.
74func TestNoFragmentOfAnAddressReachesTheLog(t *testing.T) {
75 const (
76 kelvin = "Kelvin@example.com" // ordinary ASCII K — not the Kelvin sign
77 ines = "\u0130nes@example.com" // LATIN CAPITAL LETTER I WITH DOT ABOVE
78 )
79 inner := failingSender{err: fmt.Errorf("rastrillo/mail: send to %s: %w", kelvin,
80 smtpish{msg: "550 KKKKKKKKK unknown recipients " + kelvin + ", " + ines})}
81 logger, buf := capture()
82
83 err := RedactMailErrors(inner, logger).Send(context.Background(), kelvin, "s", "b")
84 if err == nil {
85 t.Fatal("a failed send answered nil")
86 }
87 if err.Error() != "calls: mail delivery failed" {
88 t.Fatalf("returned error is %q, want exactly the fixed sentence", err)
89 }
90
91 log := buf.String()
92 if strings.Contains(log, "@") {
93 t.Fatalf("the log contains an @:\n%s", log)
94 }
95 for _, fragment := range []string{
96 kelvin, ines,
97 strings.ToLower(kelvin), strings.ToLower(ines),
98 "elvin", "nes", // the local parts, minus the leading trap character
99 "example.com",
100 "\u0130", // the trap rune itself
101 } {
102 if strings.Contains(log, fragment) {
103 t.Errorf("the log carries a fragment of an address, %q:\n%s", fragment, log)
104 }
105 }
106 if !strings.Contains(log, "address_seen=true") {
107 t.Errorf("the address was in the message and the log does not say so:\n%s", log)
108 }
109 if strings.ContainsRune(log, '\uFFFD') {
110 t.Errorf("the log holds a replacement rune, so something was cut mid-rune:\n%s", log)
111 }
112}
113
114func TestASuccessfulSendIsUntouched(t *testing.T) {
115 logger, buf := capture()
116 if err := RedactMailErrors(failingSender{}, logger).Send(context.Background(), "a@b.test", "s", "b"); err != nil {
117 t.Fatal(err)
118 }
119 if buf.Len() != 0 {
120 t.Fatalf("a successful send logged:\n%s", buf.String())
121 }
122 if RedactMailErrors(nil, logger) != nil {
123 t.Fatal("a nil mailer was wrapped into a non-nil one, which would defeat the production refusal")
124 }
125}
126
127func TestAddressSeenIsCaseInsensitiveAndNeedsNoSlicing(t *testing.T) {
128 for _, tc := range []struct {
129 msg, address string
130 want bool
131 }{
132 {"send to a@b.test: refused", "A@B.test", true}, // the recipient, folded
133 {"send to a@b.test: refused", "c@d.test", false},
134 {"dial tcp 10.0.0.1:25: connection refused", "a@b.test", false},
135 {"connection reset", "", false},
136 {"550 " + "\u212A" + "elvin@example.com not found", "\u212A" + "elvin@example.com", true},
137 } {
138 if got := addressSeen(tc.msg, tc.address); got != tc.want {
139 t.Errorf("addressSeen(%q, %q) = %v, want %v", tc.msg, tc.address, got, tc.want)
140 }
141 }
142}
143
144// mail.IsConfigured asks a Sender whether it really delivers. The
145// wrapper has to forward that, or wrapping a logging mailer would make
146// it look like a relay (mail/mail.go:59).
147func TestTheWrapperForwardsDeliversAndSendList(t *testing.T) {
148 logger, buf := capture()
149
150 logged := RedactMailErrors(mail.Logged(logger), logger)
151 if mail.IsConfigured(logged) {
152 t.Fatal("a wrapped logging mailer claims to deliver")
153 }
154 if _, ok := logged.(mail.ListSender); !ok {
155 t.Fatal("a wrapped ListSender lost SendList")
156 }
157 if err := logged.(mail.ListSender).SendList(context.Background(),
158 mail.ListMessage{To: "a@b.test", Subject: "s", Body: "b"}); err != nil {
159 t.Fatal(err)
160 }
161 buf.Reset()
162
163 plain := RedactMailErrors(failingSender{}, logger)
164 if !mail.IsConfigured(plain) {
165 t.Fatal("a wrapped plain Sender is assumed not to deliver")
166 }
167 if _, ok := plain.(mail.ListSender); ok {
168 t.Fatal("a wrapped plain Sender gained a SendList it cannot honour")
169 }
170
171 // A failing SendList redacts exactly as Send does.
172 failing := RedactMailErrors(failingListSender{err: fmt.Errorf("send to a@b.test: %w", smtpish{"550"})}, logger)
173 err := failing.(mail.ListSender).SendList(context.Background(), mail.ListMessage{To: "a@b.test"})
174 if err == nil || err.Error() != "calls: mail delivery failed" {
175 t.Fatalf("SendList returned %v", err)
176 }
177 if strings.Contains(buf.String(), "a@b.test") {
178 t.Fatalf("SendList's log line carries the address:\n%s", buf.String())
179 }
180}
181
182type failingListSender struct{ err error }
183
184func (f failingListSender) Send(ctx context.Context, to, subject, body string) error { return f.err }
185func (f failingListSender) SendList(ctx context.Context, m mail.ListMessage) error { return f.err }
186