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 strings.ToLower(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) {
189 s.blockList = []string{"reject@mail.com"}
193 conn := createClient(t, l.Addr())
194 readCodeLine(t, conn, 220)
196 runTableTest(t, conn, []requestResponse{
198 {"ehLO test.TEST", 0, func(t testing.TB, conn *textproto.Conn) { conn.ReadResponse(250) }},
199 {"mail FROM:<sender@example.com>", 250, nil},
200 {"RcPT tO:<receive@mail.com>", 250, nil},
201 {"RCPT TO:<reject@MAIL.com>", 550, nil},
202 {"RCPT TO:<reject@mail.com>", 550, nil},
203 {"DATa", 0, func(t testing.TB, conn *textproto.Conn) {
204 readCodeLine(t, conn, 354)
206 ok(t, conn.PrintfLine("."))
207 readCodeLine(t, conn, 250)
209 {"MAIL FR:", 501, nil},
214 func TestGetReceivedInfo(t *testing.T) {
216 server: &testServer{},
217 remoteAddr: &net.IPAddr{net.IPv4(127, 0, 0, 1), ""},
223 const line1 = "Received: from remote.test. (localhost [127.0.0.1])" + crlf
224 const line2 = "by Test-Server (mailpopbox) with "
225 const msgId = "abcdef.hijk"
226 lineLast := now.Format(time.RFC1123Z) + crlf
240 {params{"remote.test.", true, false, "foo@bar.com"},
242 line2 + "ESMTP id " + msgId + crlf,
243 "for <foo@bar.com>" + crlf,
244 "(using PLAINTEXT);" + crlf,
248 for _, test := range tests {
249 t.Logf("%#v", test.params)
251 conn.ehlo = test.params.ehlo
252 conn.esmtp = test.params.esmtp
253 //conn.tls = test.params.tls
255 envelope := Envelope{
256 RcptTo: []mail.Address{{"", test.params.address}},
261 actual := conn.getReceivedInfo(envelope)
262 actualLines := strings.SplitAfter(string(actual), crlf)
264 if len(actualLines) != len(test.expect) {
265 t.Errorf("wrong numbber of lines, expected %d, got %d", len(test.expect), len(actualLines))
269 for i, line := range actualLines {
270 expect := test.expect[i]
271 if expect != strings.TrimLeft(line, " ") {
272 t.Errorf("Expected equal string %q, got %q", expect, line)