15 "github.com/uber-go/zap"
18 func _fl(depth int) string {
19 _, file, line, _ := runtime.Caller(depth + 1)
20 return fmt.Sprintf("[%s:%d]", filepath.Base(file), line)
23 func ok(t testing.TB, err error) {
25 t.Errorf("%s unexpected error: %v", _fl(1), err)
29 func responseOK(t testing.TB, conn *textproto.Conn) string {
30 line, err := conn.ReadLine()
32 t.Errorf("%s responseOK: %v", _fl(1), err)
34 if !strings.HasPrefix(line, "+OK") {
35 t.Errorf("%s expected +OK, got %q", _fl(1), line)
40 func responseERR(t testing.TB, conn *textproto.Conn) string {
41 line, err := conn.ReadLine()
43 t.Errorf("%s responseERR: %v", _fl(1), err)
45 if !strings.HasPrefix(line, "-ERR") {
46 t.Errorf("%s expected -ERR, got %q", _fl(1), line)
51 func runServer(t *testing.T, po PostOffice) net.Listener {
52 l, err := net.Listen("tcp", "localhost:0")
60 conn, err := l.Accept()
64 go AcceptConnection(conn, po, zap.New(zap.NullEncoder()))
70 type testServer struct {
75 func (s *testServer) Name() string {
79 func (s *testServer) OpenMailbox(user, pass string) (Mailbox, error) {
80 if s.user == user && s.pass == pass {
83 return nil, fmt.Errorf("bad username/pass")
86 type testMailbox struct {
87 msgs map[int]*testMessage
90 func (mb *testMailbox) ListMessages() ([]Message, error) {
91 msgs := make([]Message, 0, len(mb.msgs))
92 for i, _ := range mb.msgs {
93 msgs = append(msgs, mb.msgs[i])
98 func (mb *testMailbox) GetMessage(id int) Message {
99 if msg, ok := mb.msgs[id]; ok {
105 func (mb *testMailbox) Retrieve(msg Message) (io.ReadCloser, error) {
106 r := strings.NewReader(msg.(*testMessage).body)
107 return ioutil.NopCloser(r), nil
110 func (mb *testMailbox) Delete(msg Message) error {
111 msg.(*testMessage).deleted = true
115 func (mb *testMailbox) Close() error {
119 func (mb *testMailbox) Reset() {
120 for _, msg := range mb.msgs {
125 type testMessage struct {
132 func (m *testMessage) UniqueID() string {
133 return fmt.Sprintf("%p", m)
136 func (m *testMessage) ID() int {
139 func (m *testMessage) Size() int {
142 func (m *testMessage) Deleted() bool {
146 func newTestServer() *testServer {
151 msgs: make(map[int]*testMessage),
157 func TestExampleSession(t *testing.T) {
162 s.mb.msgs[1] = &testMessage{1, 120, false, ""}
163 s.mb.msgs[2] = &testMessage{2, 200, false, ""}
165 conn, err := textproto.Dial(l.Addr().Network(), l.Addr().String())
168 line := responseOK(t, conn)
169 if !strings.Contains(line, s.Name()) {
170 t.Errorf("POP greeting did not include server name, got %q", line)
173 ok(t, conn.PrintfLine("USER u"))
176 ok(t, conn.PrintfLine("PASS p"))
179 ok(t, conn.PrintfLine("STAT"))
180 line = responseOK(t, conn)
181 expected := "+OK 2 320"
182 if line != expected {
183 t.Errorf("STAT expected %q, got %q", expected, line)
186 ok(t, conn.PrintfLine("LIST"))
188 lines, err := conn.ReadDotLines()
191 t.Errorf("LIST expected 2 lines, got %d", len(lines))
194 if lines[0] != expected {
195 t.Errorf("LIST line 0 expected %q, got %q", expected, lines[0])
198 if lines[1] != expected {
199 t.Errorf("LIST line 1 expected %q, got %q", expected, lines[1])
202 ok(t, conn.PrintfLine("QUIT"))
206 type requestResponse struct {
208 expecter func(testing.TB, *textproto.Conn) string
211 func expectOKResponse(predicate func(string) bool) func(testing.TB, *textproto.Conn) string {
212 return func(t testing.TB, conn *textproto.Conn) string {
213 line := responseOK(t, conn)
214 if !predicate(line) {
215 t.Errorf("%s Predicate failed, got %q", _fl(1), line)
221 func clientServerTest(t *testing.T, s *testServer, sequence []requestResponse) {
225 conn, err := textproto.Dial(l.Addr().Network(), l.Addr().String())
230 for _, pair := range sequence {
231 ok(t, conn.PrintfLine(pair.command))
232 pair.expecter(t, conn)
234 t.Logf("command %q", pair.command)
239 func TestAuthStates(t *testing.T) {
240 clientServerTest(t, newTestServer(), []requestResponse{
241 {"STAT", responseERR},
242 {"NOOP", responseOK},
243 {"USER bad", responseOK},
244 {"PASS bad", responseERR},
245 {"LIST", responseERR},
246 {"USER u", responseOK},
247 {"PASS bad", responseERR},
248 {"STAT", responseERR},
249 {"PASS p", responseOK},
250 {"QUIT", responseOK},
254 func TestDeleted(t *testing.T) {
256 s.mb.msgs[1] = &testMessage{1, 999, false, ""}
257 s.mb.msgs[2] = &testMessage{2, 10, false, ""}
259 clientServerTest(t, s, []requestResponse{
260 {"USER u", responseOK},
261 {"PASS p", responseOK},
262 {"STAT", expectOKResponse(func(s string) bool {
263 return s == "+OK 2 1009"
265 {"DELE 1", responseOK},
266 {"RETR 1", responseERR},
267 {"DELE 1", responseERR},
268 {"STAT", expectOKResponse(func(s string) bool {
269 return s == "+OK 1 10"
271 {"RSET", responseOK},
272 {"STAT", expectOKResponse(func(s string) bool {
273 return s == "+OK 2 1009"
275 {"QUIT", responseOK},
279 func TestCaseSensitivty(t *testing.T) {
281 s.mb.msgs[999] = &testMessage{999, 1, false, "a"}
283 clientServerTest(t, s, []requestResponse{
284 {"user u", responseOK},
285 {"PasS p", responseOK},
286 {"sTaT", responseOK},
287 {"retr 1", responseERR},
288 {"dele 999", responseOK},
289 {"QUIT", responseOK},
293 func TestRetr(t *testing.T) {
295 s.mb.msgs[1] = &testMessage{1, 5, false, "hello"}
296 s.mb.msgs[2] = &testMessage{2, 69, false, "this\r\nis a\r\n.\r\ntest"}
298 clientServerTest(t, s, []requestResponse{
299 {"USER u", responseOK},
300 {"PASS p", responseOK},
301 {"STAT", responseOK},
302 {"RETR 1", func(t testing.TB, tp *textproto.Conn) string {
308 resp, err := tp.ReadDotLines()
314 expected := []string{"hello"}
315 if !reflect.DeepEqual(resp, expected) {
316 t.Errorf("Expected %v, got %v", expected, resp)
321 {"RETR 2", func(t testing.TB, tp *textproto.Conn) string {
327 resp, err := tp.ReadDotLines()
333 expected := []string{"this", "is a", ".", "test"}
334 if !reflect.DeepEqual(resp, expected) {
335 t.Errorf("Expected %v, got %v", expected, resp)
340 {"QUIT", responseOK},
344 func TestUidl(t *testing.T) {
346 s.mb.msgs[1] = &testMessage{1, 3, false, "abc"}
347 s.mb.msgs[2] = &testMessage{2, 1, true, "Z"}
348 s.mb.msgs[3] = &testMessage{3, 4, false, "test"}
350 clientServerTest(t, s, []requestResponse{
351 {"USER u", responseOK},
352 {"PASS p", responseOK},
353 {"UIDL", func(t testing.TB, tp *textproto.Conn) string {
359 resp, err := tp.ReadDotLines()
365 expected := []string{
366 fmt.Sprintf("1 %p", s.mb.msgs[1]),
367 fmt.Sprintf("3 %p", s.mb.msgs[3]),
369 if !reflect.DeepEqual(resp, expected) {
370 t.Errorf("Expected %v, got %v", expected, resp)
375 {"QUIT", responseOK},