Ensure stable message list order in pop3/conn_test.go.
[mailpopbox.git] / pop3 / conn_test.go
1 package pop3
2
3 import (
4 "fmt"
5 "io"
6 "io/ioutil"
7 "net"
8 "net/textproto"
9 "path/filepath"
10 "reflect"
11 "runtime"
12 "sort"
13 "strings"
14 "testing"
15
16 "github.com/uber-go/zap"
17 )
18
19 func _fl(depth int) string {
20 _, file, line, _ := runtime.Caller(depth + 1)
21 return fmt.Sprintf("[%s:%d]", filepath.Base(file), line)
22 }
23
24 func ok(t testing.TB, err error) {
25 if err != nil {
26 t.Errorf("%s unexpected error: %v", _fl(1), err)
27 }
28 }
29
30 func responseOK(t testing.TB, conn *textproto.Conn) string {
31 line, err := conn.ReadLine()
32 if err != nil {
33 t.Errorf("%s responseOK: %v", _fl(1), err)
34 }
35 if !strings.HasPrefix(line, "+OK") {
36 t.Errorf("%s expected +OK, got %q", _fl(1), line)
37 }
38 return line
39 }
40
41 func responseERR(t testing.TB, conn *textproto.Conn) string {
42 line, err := conn.ReadLine()
43 if err != nil {
44 t.Errorf("%s responseERR: %v", _fl(1), err)
45 }
46 if !strings.HasPrefix(line, "-ERR") {
47 t.Errorf("%s expected -ERR, got %q", _fl(1), line)
48 }
49 return line
50 }
51
52 func runServer(t *testing.T, po PostOffice) net.Listener {
53 l, err := net.Listen("tcp", "localhost:0")
54 if err != nil {
55 t.Fatal(err)
56 return nil
57 }
58
59 go func() {
60 for {
61 conn, err := l.Accept()
62 if err != nil {
63 return
64 }
65 go AcceptConnection(conn, po, zap.New(zap.NullEncoder()))
66 }
67 }()
68 return l
69 }
70
71 type testServer struct {
72 user, pass string
73 mb testMailbox
74 }
75
76 func (s *testServer) Name() string {
77 return "Test-Server"
78 }
79
80 func (s *testServer) OpenMailbox(user, pass string) (Mailbox, error) {
81 if s.user == user && s.pass == pass {
82 return &s.mb, nil
83 }
84 return nil, fmt.Errorf("bad username/pass")
85 }
86
87 type testMailbox struct {
88 msgs map[int]*testMessage
89 }
90
91 type MessageList []Message
92
93 func (l MessageList) Len() int {
94 return len(l)
95 }
96 func (l MessageList) Less(i, j int) bool {
97 return l[i].ID() < l[j].ID()
98 }
99 func (l MessageList) Swap(i, j int) {
100 l[i], l[j] = l[j], l[i]
101 }
102
103 func (mb *testMailbox) ListMessages() ([]Message, error) {
104 msgs := make([]Message, 0, len(mb.msgs))
105 for i, _ := range mb.msgs {
106 msgs = append(msgs, mb.msgs[i])
107 }
108 sort.Sort(MessageList(msgs))
109 return msgs, nil
110 }
111
112 func (mb *testMailbox) GetMessage(id int) Message {
113 if msg, ok := mb.msgs[id]; ok {
114 return msg
115 }
116 return nil
117 }
118
119 func (mb *testMailbox) Retrieve(msg Message) (io.ReadCloser, error) {
120 r := strings.NewReader(msg.(*testMessage).body)
121 return ioutil.NopCloser(r), nil
122 }
123
124 func (mb *testMailbox) Delete(msg Message) error {
125 msg.(*testMessage).deleted = true
126 return nil
127 }
128
129 func (mb *testMailbox) Close() error {
130 return nil
131 }
132
133 func (mb *testMailbox) Reset() {
134 for _, msg := range mb.msgs {
135 msg.deleted = false
136 }
137 }
138
139 type testMessage struct {
140 id int
141 size int
142 deleted bool
143 body string
144 }
145
146 func (m *testMessage) UniqueID() string {
147 return fmt.Sprintf("%p", m)
148 }
149
150 func (m *testMessage) ID() int {
151 return m.id
152 }
153 func (m *testMessage) Size() int {
154 return m.size
155 }
156 func (m *testMessage) Deleted() bool {
157 return m.deleted
158 }
159
160 func newTestServer() *testServer {
161 return &testServer{
162 user: "u",
163 pass: "p",
164 mb: testMailbox{
165 msgs: make(map[int]*testMessage),
166 },
167 }
168 }
169
170 // RFC 1939 ยง 10
171 func TestExampleSession(t *testing.T) {
172 s := newTestServer()
173 l := runServer(t, s)
174 defer l.Close()
175
176 s.mb.msgs[1] = &testMessage{1, 120, false, ""}
177 s.mb.msgs[2] = &testMessage{2, 200, false, ""}
178
179 conn, err := textproto.Dial(l.Addr().Network(), l.Addr().String())
180 ok(t, err)
181
182 line := responseOK(t, conn)
183 if !strings.Contains(line, s.Name()) {
184 t.Errorf("POP greeting did not include server name, got %q", line)
185 }
186
187 ok(t, conn.PrintfLine("USER u"))
188 responseOK(t, conn)
189
190 ok(t, conn.PrintfLine("PASS p"))
191 responseOK(t, conn)
192
193 ok(t, conn.PrintfLine("STAT"))
194 line = responseOK(t, conn)
195 expected := "+OK 2 320"
196 if line != expected {
197 t.Errorf("STAT expected %q, got %q", expected, line)
198 }
199
200 ok(t, conn.PrintfLine("LIST"))
201 responseOK(t, conn)
202 lines, err := conn.ReadDotLines()
203 ok(t, err)
204 if len(lines) != 2 {
205 t.Errorf("LIST expected 2 lines, got %d", len(lines))
206 }
207 expected = "1 120"
208 if lines[0] != expected {
209 t.Errorf("LIST line 0 expected %q, got %q", expected, lines[0])
210 }
211 expected = "2 200"
212 if lines[1] != expected {
213 t.Errorf("LIST line 1 expected %q, got %q", expected, lines[1])
214 }
215
216 ok(t, conn.PrintfLine("QUIT"))
217 responseOK(t, conn)
218 }
219
220 type requestResponse struct {
221 command string
222 expecter func(testing.TB, *textproto.Conn) string
223 }
224
225 func expectOKResponse(predicate func(string) bool) func(testing.TB, *textproto.Conn) string {
226 return func(t testing.TB, conn *textproto.Conn) string {
227 line := responseOK(t, conn)
228 if !predicate(line) {
229 t.Errorf("%s Predicate failed, got %q", _fl(1), line)
230 }
231 return line
232 }
233 }
234
235 func clientServerTest(t *testing.T, s *testServer, sequence []requestResponse) {
236 l := runServer(t, s)
237 defer l.Close()
238
239 conn, err := textproto.Dial(l.Addr().Network(), l.Addr().String())
240 ok(t, err)
241
242 responseOK(t, conn)
243
244 for _, pair := range sequence {
245 ok(t, conn.PrintfLine(pair.command))
246 pair.expecter(t, conn)
247 if t.Failed() {
248 t.Logf("command %q", pair.command)
249 }
250 }
251 }
252
253 func TestAuthStates(t *testing.T) {
254 clientServerTest(t, newTestServer(), []requestResponse{
255 {"STAT", responseERR},
256 {"NOOP", responseOK},
257 {"USER bad", responseOK},
258 {"PASS bad", responseERR},
259 {"USER", responseERR},
260 {"USER x", responseOK},
261 {"PASS", responseERR},
262 {"LIST", responseERR},
263 {"USER u", responseOK},
264 {"PASS bad", responseERR},
265 {"STAT", responseERR},
266 {"PASS p", responseOK},
267 {"QUIT", responseOK},
268 })
269 }
270
271 func TestDeleted(t *testing.T) {
272 s := newTestServer()
273 s.mb.msgs[1] = &testMessage{1, 999, false, ""}
274 s.mb.msgs[2] = &testMessage{2, 10, false, ""}
275
276 clientServerTest(t, s, []requestResponse{
277 {"USER u", responseOK},
278 {"PASS p", responseOK},
279 {"STAT", expectOKResponse(func(s string) bool {
280 return s == "+OK 2 1009"
281 })},
282 {"DELE 1", responseOK},
283 {"RETR 1", responseERR},
284 {"DELE 1", responseERR},
285 {"STAT", expectOKResponse(func(s string) bool {
286 return s == "+OK 1 10"
287 })},
288 {"RSET", responseOK},
289 {"STAT", expectOKResponse(func(s string) bool {
290 return s == "+OK 2 1009"
291 })},
292 {"QUIT", responseOK},
293 })
294 }
295
296 func TestCaseSensitivty(t *testing.T) {
297 s := newTestServer()
298 s.mb.msgs[999] = &testMessage{999, 1, false, "a"}
299
300 clientServerTest(t, s, []requestResponse{
301 {"user u", responseOK},
302 {"PasS p", responseOK},
303 {"sTaT", responseOK},
304 {"retr 1", responseERR},
305 {"dele 999", responseOK},
306 {"QUIT", responseOK},
307 })
308 }
309
310 func TestRetr(t *testing.T) {
311 s := newTestServer()
312 s.mb.msgs[1] = &testMessage{1, 5, false, "hello"}
313 s.mb.msgs[2] = &testMessage{2, 69, false, "this\r\nis a\r\n.\r\ntest"}
314
315 clientServerTest(t, s, []requestResponse{
316 {"USER u", responseOK},
317 {"PASS p", responseOK},
318 {"STAT", responseOK},
319 {"RETR 1", func(t testing.TB, tp *textproto.Conn) string {
320 responseOK(t, tp)
321 if t.Failed() {
322 return ""
323 }
324
325 resp, err := tp.ReadDotLines()
326 if err != nil {
327 t.Error(err)
328 return ""
329 }
330
331 expected := []string{"hello"}
332 if !reflect.DeepEqual(resp, expected) {
333 t.Errorf("Expected %v, got %v", expected, resp)
334 }
335
336 return ""
337 }},
338 {"RETR 2", func(t testing.TB, tp *textproto.Conn) string {
339 responseOK(t, tp)
340 if t.Failed() {
341 return ""
342 }
343
344 resp, err := tp.ReadDotLines()
345 if err != nil {
346 t.Error(err)
347 return ""
348 }
349
350 expected := []string{"this", "is a", ".", "test"}
351 if !reflect.DeepEqual(resp, expected) {
352 t.Errorf("Expected %v, got %v", expected, resp)
353 }
354
355 return ""
356 }},
357 {"QUIT", responseOK},
358 })
359 }
360
361 func TestUidl(t *testing.T) {
362 s := newTestServer()
363 s.mb.msgs[1] = &testMessage{1, 3, false, "abc"}
364 s.mb.msgs[2] = &testMessage{2, 1, true, "Z"}
365 s.mb.msgs[3] = &testMessage{3, 4, false, "test"}
366
367 clientServerTest(t, s, []requestResponse{
368 {"USER u", responseOK},
369 {"PASS p", responseOK},
370 {"UIDL", func(t testing.TB, tp *textproto.Conn) string {
371 responseOK(t, tp)
372 if t.Failed() {
373 return ""
374 }
375
376 resp, err := tp.ReadDotLines()
377 if err != nil {
378 t.Error(err)
379 return ""
380 }
381
382 expected := []string{
383 fmt.Sprintf("1 %p", s.mb.msgs[1]),
384 fmt.Sprintf("3 %p", s.mb.msgs[3]),
385 }
386 if !reflect.DeepEqual(resp, expected) {
387 t.Errorf("Expected %v, got %v", expected, resp)
388 }
389
390 return ""
391 }},
392 {"QUIT", responseOK},
393 })
394 }
395
396 func TestDele(t *testing.T) {
397 s := newTestServer()
398 s.mb.msgs[1] = &testMessage{1, 3, false, "abc"}
399 s.mb.msgs[2] = &testMessage{2, 1, false, "d"}
400
401 clientServerTest(t, s, []requestResponse{
402 {"USER u", responseOK},
403 {"PASS p", responseOK},
404 {"STAT", expectOKResponse(func(s string) bool {
405 return s == "+OK 2 4"
406 })},
407 {"DELE 1", responseOK},
408 {"STAT", expectOKResponse(func(s string) bool {
409 return s == "+OK 1 1"
410 })},
411 {"RSET", responseOK},
412 {"STAT", expectOKResponse(func(s string) bool {
413 return s == "+OK 2 4"
414 })},
415 {"QUIT", responseOK},
416 })
417
418 if s.mb.msgs[1].Deleted() || s.mb.msgs[2].Deleted() {
419 t.Errorf("RSET should not delete a message")
420 }
421
422 clientServerTest(t, s, []requestResponse{
423 {"USER u", responseOK},
424 {"PASS p", responseOK},
425 {"STAT", expectOKResponse(func(s string) bool {
426 return s == "+OK 2 4"
427 })},
428 {"DELE 1", responseOK},
429 {"STAT", expectOKResponse(func(s string) bool {
430 return s == "+OK 1 1"
431 })},
432 {"QUIT", responseOK},
433 })
434
435 if !s.mb.msgs[1].Deleted() {
436 t.Errorf("DELE did not work")
437 }
438 if s.mb.msgs[2].Deleted() {
439 t.Errorf("DELE the wrong message")
440 }
441 }
442
443 func TestCapa(t *testing.T) {
444 s := newTestServer()
445
446 capaTest := func(t testing.TB, tp *textproto.Conn) string {
447 responseOK(t, tp)
448 if t.Failed() {
449 return ""
450 }
451
452 resp, err := tp.ReadDotLines()
453 if err != nil {
454 t.Error(err)
455 return ""
456 }
457
458 const (
459 capNeeded = iota
460 capSeen
461 capOK
462 )
463
464 caps := map[string]int{
465 "USER": capNeeded,
466 "UIDL": capNeeded,
467 }
468 for _, line := range resp {
469 if val, ok := caps[line]; ok {
470 if val == capNeeded {
471 caps[line] = capOK
472 } else {
473 t.Errorf("unxpected capa value %q", line)
474 }
475 } else {
476 caps[line] = capSeen
477 }
478 }
479 for c, val := range caps {
480 if val != capOK {
481 t.Errorf("unexpected capa value for %q: %d", c, val)
482 }
483 }
484 return ""
485 }
486
487 clientServerTest(t, s, []requestResponse{
488 {"CAPA", capaTest},
489 {"USER u", responseOK},
490 {"CAPA", capaTest},
491 {"PASS p", responseOK},
492 {"CAPA", capaTest},
493 {"QUIT", responseOK},
494 })
495 }