14 "github.com/uber-go/zap"
17 func _fl(depth int) string {
18 _, file, line, _ := runtime.Caller(depth + 1)
19 return fmt.Sprintf("[%s:%d]", filepath.Base(file), line)
22 func ok(t testing.TB, err error) {
24 t.Errorf("%s unexpected error: %v", _fl(1), err)
28 func readCodeLine(t testing.TB, conn *textproto.Conn, code int) string {
29 _, message, err := conn.ReadCodeLine(code)
31 t.Errorf("%s ReadCodeLine error: %v", _fl(1), err)
36 // runServer creates a TCP socket, runs a listening server, and returns the connection.
37 // The server exits when the Conn is closed.
38 func runServer(t *testing.T, server Server) net.Listener {
39 l, err := net.Listen("tcp", "localhost:0")
47 conn, err := l.Accept()
51 go AcceptConnection(conn, server, zap.New(zap.NullEncoder()))
58 type testServer struct {
63 func (s *testServer) Name() string {
67 func (s *testServer) VerifyAddress(addr mail.Address) ReplyLine {
68 for _, block := range s.blockList {
69 if block == addr.Address {
70 return ReplyBadMailbox
76 func createClient(t *testing.T, addr net.Addr) *textproto.Conn {
77 conn, err := textproto.Dial(addr.Network(), addr.String())
85 type requestResponse struct {
88 handler func(testing.TB, *textproto.Conn)
91 func runTableTest(t testing.TB, conn *textproto.Conn, seq []requestResponse) {
92 for i, rr := range seq {
93 t.Logf("%s case %d", _fl(1), i)
94 ok(t, conn.PrintfLine(rr.request))
95 if rr.handler != nil {
98 readCodeLine(t, conn, rr.responseCode)
104 func TestScenarioTypical(t *testing.T) {
106 blockList: []string{"Green@foo.com"},
108 l := runServer(t, &s)
111 conn := createClient(t, l.Addr())
113 message := readCodeLine(t, conn, 220)
114 if !strings.HasPrefix(message, s.Name()) {
115 t.Errorf("Greeting does not have server name, got %q", message)
118 greet := "greeting.TestScenarioTypical"
119 ok(t, conn.PrintfLine("EHLO "+greet))
121 _, message, err := conn.ReadResponse(250)
123 if !strings.Contains(message, greet) {
124 t.Errorf("EHLO response does not contain greeting, got %q", message)
127 ok(t, conn.PrintfLine("MAIL FROM:<Smith@bar.com>"))
128 readCodeLine(t, conn, 250)
130 ok(t, conn.PrintfLine("RCPT TO:<Jones@foo.com>"))
131 readCodeLine(t, conn, 250)
133 ok(t, conn.PrintfLine("RCPT TO:<Green@foo.com>"))
134 readCodeLine(t, conn, 550)
136 ok(t, conn.PrintfLine("RCPT TO:<Brown@foo.com>"))
137 readCodeLine(t, conn, 250)
139 ok(t, conn.PrintfLine("DATA"))
140 readCodeLine(t, conn, 354)
142 ok(t, conn.PrintfLine("Blah blah blah..."))
143 ok(t, conn.PrintfLine("...etc. etc. etc."))
144 ok(t, conn.PrintfLine("."))
145 readCodeLine(t, conn, 250)
147 ok(t, conn.PrintfLine("QUIT"))
148 readCodeLine(t, conn, 221)
151 func TestVerifyAddress(t *testing.T) {
153 blockList: []string{"banned@test.mail"},
155 l := runServer(t, &s)
158 conn := createClient(t, l.Addr())
159 readCodeLine(t, conn, 220)
161 runTableTest(t, conn, []requestResponse{
162 {"EHLO test", 0, func(t testing.TB, conn *textproto.Conn) { conn.ReadResponse(250) }},
163 {"VRFY banned@test.mail", 252, nil},
164 {"VRFY allowed@test.mail", 252, nil},
165 {"MAIL FROM:<sender@example.com>", 250, nil},
166 {"RCPT TO:<banned@test.mail>", 550, nil},
171 func TestBadAddress(t *testing.T) {
172 l := runServer(t, &testServer{})
175 conn := createClient(t, l.Addr())
176 readCodeLine(t, conn, 220)
178 runTableTest(t, conn, []requestResponse{
179 {"EHLO test", 0, func(t testing.TB, conn *textproto.Conn) { conn.ReadResponse(250) }},
180 {"MAIL FROM:<sender>", 501, nil},
181 {"MAIL FROM:<sender@foo.com> SIZE=2163", 250, nil},
182 {"RCPT TO:<banned.net>", 501, nil},
187 func TestCaseSensitivty(t *testing.T) {
192 conn := createClient(t, l.Addr())
193 readCodeLine(t, conn, 220)
195 runTableTest(t, conn, []requestResponse{
197 {"ehLO test.TEST", 0, func(t testing.TB, conn *textproto.Conn) { conn.ReadResponse(250) }},
198 {"mail FROM:<sender@example.com>", 250, nil},
199 {"RcPT tO:<receive@mail.com>", 250, nil},
200 {"DATa", 0, func(t testing.TB, conn *textproto.Conn) {
201 readCodeLine(t, conn, 354)
203 ok(t, conn.PrintfLine("."))
204 readCodeLine(t, conn, 250)
206 {"MAIL FR:", 501, nil},
211 func TestGetReceivedInfo(t *testing.T) {
213 server: &testServer{},
214 remoteAddr: &net.IPAddr{net.IPv4(127, 0, 0, 1), ""},
220 const line1 = "Received: from remote.test. (localhost [127.0.0.1])" + crlf
221 const line2 = "by Test-Server (mailpopbox) with "
222 const msgId = "abcdef.hijk"
223 lineLast := now.Format(time.RFC1123Z) + crlf
237 {params{"remote.test.", true, false, "foo@bar.com"},
239 line2 + "ESMTP id " + msgId + crlf,
240 "for <foo@bar.com>" + crlf,
241 "(using PLAINTEXT);" + crlf,
245 for _, test := range tests {
246 t.Logf("%#v", test.params)
248 conn.ehlo = test.params.ehlo
249 conn.esmtp = test.params.esmtp
250 //conn.tls = test.params.tls
252 envelope := Envelope{
253 RcptTo: []mail.Address{{"", test.params.address}},
258 actual := conn.getReceivedInfo(envelope)
259 actualLines := strings.SplitAfter(string(actual), crlf)
261 if len(actualLines) != len(test.expect) {
262 t.Errorf("wrong numbber of lines, expected %d, got %d", len(test.expect), len(actualLines))
266 for i, line := range actualLines {
267 expect := test.expect[i]
268 if expect != strings.TrimLeft(line, " ") {
269 t.Errorf("Expected equal string %q, got %q", expect, line)