]>
src.bluestatic.org Git - mailpopbox.git/blob - pop3/conn_test.go
2 // Copyright 2020 Blue Static <https://www.bluestatic.org>
3 // This program is free software licensed under the GNU General Public License,
4 // version 3.0. The full text of the license can be found in LICENSE.txt.
5 // SPDX-License-Identifier: GPL-3.0-only
25 func _fl(depth
int) string {
26 _
, file
, line
, _
:= runtime
.Caller(depth
+ 1)
27 return fmt
.Sprintf("[%s:%d]", filepath
.Base(file
), line
)
30 func ok(t testing
.TB
, err error
) {
32 t
.Errorf("%s unexpected error: %v", _fl(1), err
)
36 func responseOK(t testing
.TB
, conn
*textproto
.Conn
) string {
37 line
, err
:= conn
.ReadLine()
39 t
.Errorf("%s responseOK: %v", _fl(1), err
)
41 if !strings
.HasPrefix(line
, "+OK") {
42 t
.Errorf("%s expected +OK, got %q", _fl(1), line
)
47 func responseERR(t testing
.TB
, conn
*textproto
.Conn
) string {
48 line
, err
:= conn
.ReadLine()
50 t
.Errorf("%s responseERR: %v", _fl(1), err
)
52 if !strings
.HasPrefix(line
, "-ERR") {
53 t
.Errorf("%s expected -ERR, got %q", _fl(1), line
)
58 func runServer(t
*testing
.T
, po PostOffice
) net
.Listener
{
59 l
, err
:= net
.Listen("tcp", "localhost:0")
67 conn
, err
:= l
.Accept()
71 go AcceptConnection(conn
, po
, zap
.NewNop())
77 type testServer
struct {
82 func (s
*testServer
) Name() string {
86 func (s
*testServer
) OpenMailbox(user
, pass
string) (Mailbox
, error
) {
87 if s
.user
== user
&& s
.pass
== pass
{
90 return nil, fmt
.Errorf("bad username/pass")
93 type testMailbox
struct {
94 msgs
map[int]*testMessage
97 type MessageList
[]Message
99 func (l MessageList
) Len() int {
102 func (l MessageList
) Less(i
, j
int) bool {
103 return l
[i
].ID() < l
[j
].ID()
105 func (l MessageList
) Swap(i
, j
int) {
106 l
[i
], l
[j
] = l
[j
], l
[i
]
109 func (mb
*testMailbox
) ListMessages() ([]Message
, error
) {
110 msgs
:= make([]Message
, 0, len(mb
.msgs
))
111 for i
, _
:= range mb
.msgs
{
112 msgs
= append(msgs
, mb
.msgs
[i
])
114 sort
.Sort(MessageList(msgs
))
118 func (mb
*testMailbox
) GetMessage(id
int) Message
{
119 if msg
, ok
:= mb
.msgs
[id
]; ok
{
125 func (mb
*testMailbox
) Retrieve(msg Message
) (io
.ReadCloser
, error
) {
126 r
:= strings
.NewReader(msg
.(*testMessage
).body
)
127 return ioutil
.NopCloser(r
), nil
130 func (mb
*testMailbox
) Delete(msg Message
) error
{
131 msg
.(*testMessage
).deleted
= true
135 func (mb
*testMailbox
) Close() error
{
139 func (mb
*testMailbox
) Reset() {
140 for _
, msg
:= range mb
.msgs
{
145 type testMessage
struct {
152 func (m
*testMessage
) UniqueID() string {
153 return fmt
.Sprintf("%p", m
)
156 func (m
*testMessage
) ID() int {
159 func (m
*testMessage
) Size() int {
162 func (m
*testMessage
) Deleted() bool {
166 func newTestServer() *testServer
{
171 msgs
: make(map[int]*testMessage
),
177 func TestExampleSession(t
*testing
.T
) {
182 s
.mb
.msgs
[1] = &testMessage
{1, 120, false, ""}
183 s
.mb
.msgs
[2] = &testMessage
{2, 200, false, ""}
185 conn
, err
:= textproto
.Dial(l
.Addr().Network(), l
.Addr().String())
188 line
:= responseOK(t
, conn
)
189 if !strings
.Contains(line
, s
.Name()) {
190 t
.Errorf("POP greeting did not include server name, got %q", line
)
193 ok(t
, conn
.PrintfLine("USER u"))
196 ok(t
, conn
.PrintfLine("PASS p"))
199 ok(t
, conn
.PrintfLine("STAT"))
200 if want
, got
:= "+OK 2 320", responseOK(t
, conn
); want
!= got
{
201 t
.Errorf("STAT want %q, got %q", want
, got
)
204 ok(t
, conn
.PrintfLine("LIST"))
206 lines
, err
:= conn
.ReadDotLines()
208 if want
, got
:= 2, len(lines
); want
!= got
{
209 t
.Errorf("LIST want %d lines, got %d", want
, got
)
211 if want
, got
:= "1 120", lines
[0]; want
!= got
{
212 t
.Errorf("LIST line 0 want %q, got %q", want
, got
)
214 if want
, got
:= "2 200", lines
[1]; want
!= got
{
215 t
.Errorf("LIST line 1 expected %q, got %q", want
, got
)
218 ok(t
, conn
.PrintfLine("QUIT"))
222 type requestResponse
struct {
224 expecter
func(testing
.TB
, *textproto
.Conn
) string
227 func expectOKResponse(predicate
func(string) bool) func(testing
.TB
, *textproto
.Conn
) string {
228 return func(t testing
.TB
, conn
*textproto
.Conn
) string {
229 line
:= responseOK(t
, conn
)
230 if !predicate(line
) {
231 t
.Errorf("%s Predicate failed, got %q", _fl(1), line
)
237 func clientServerTest(t
*testing
.T
, s
*testServer
, sequence
[]requestResponse
) {
241 conn
, err
:= textproto
.Dial(l
.Addr().Network(), l
.Addr().String())
246 for _
, pair
:= range sequence
{
247 ok(t
, conn
.PrintfLine(pair
.command
))
248 pair
.expecter(t
, conn
)
250 t
.Logf("command %q", pair
.command
)
255 func TestAuthStates(t
*testing
.T
) {
256 clientServerTest(t
, newTestServer(), []requestResponse
{
257 {"STAT", responseERR
},
258 {"NOOP", responseOK
},
259 {"USER bad", responseOK
},
260 {"PASS bad", responseERR
},
261 {"USER", responseERR
},
262 {"USER x", responseOK
},
263 {"PASS", responseERR
},
264 {"LIST", responseERR
},
265 {"USER u", responseOK
},
266 {"PASS bad", responseERR
},
267 {"STAT", responseERR
},
268 {"PASS p", responseOK
},
269 {"QUIT", responseOK
},
273 func TestDeleted(t
*testing
.T
) {
275 s
.mb
.msgs
[1] = &testMessage
{1, 999, false, ""}
276 s
.mb
.msgs
[2] = &testMessage
{2, 10, false, ""}
278 clientServerTest(t
, s
, []requestResponse
{
279 {"USER u", responseOK
},
280 {"PASS p", responseOK
},
281 {"STAT", expectOKResponse(func(s
string) bool {
282 return s
== "+OK 2 1009"
284 {"DELE 1", responseOK
},
285 {"RETR 1", responseERR
},
286 {"DELE 1", responseERR
},
287 {"STAT", expectOKResponse(func(s
string) bool {
288 return s
== "+OK 1 10"
290 {"RSET", responseOK
},
291 {"STAT", expectOKResponse(func(s
string) bool {
292 return s
== "+OK 2 1009"
294 {"QUIT", responseOK
},
298 func TestCaseSensitivty(t
*testing
.T
) {
300 s
.mb
.msgs
[999] = &testMessage
{999, 1, false, "a"}
302 clientServerTest(t
, s
, []requestResponse
{
303 {"user u", responseOK
},
304 {"PasS p", responseOK
},
305 {"sTaT", responseOK
},
306 {"retr 1", responseERR
},
307 {"dele 999", responseOK
},
308 {"QUIT", responseOK
},
312 func TestRetr(t
*testing
.T
) {
314 s
.mb
.msgs
[1] = &testMessage
{1, 5, false, "hello"}
315 s
.mb
.msgs
[2] = &testMessage
{2, 69, false, "this\r\nis a\r\n.\r\ntest"}
317 clientServerTest(t
, s
, []requestResponse
{
318 {"USER u", responseOK
},
319 {"PASS p", responseOK
},
320 {"STAT", responseOK
},
321 {"RETR 1", func(t testing
.TB
, tp
*textproto
.Conn
) string {
327 resp
, err
:= tp
.ReadDotLines()
333 want
:= []string{"hello"}
334 if !reflect
.DeepEqual(resp
, want
) {
335 t
.Errorf("Want %v, got %v", want
, resp
)
340 {"RETR 2", func(t testing
.TB
, tp
*textproto
.Conn
) string {
346 resp
, err
:= tp
.ReadDotLines()
352 want
:= []string{"this", "is a", ".", "test"}
353 if !reflect
.DeepEqual(resp
, want
) {
354 t
.Errorf("Want %v, got %v", want
, resp
)
359 {"QUIT", responseOK
},
363 func TestUidl(t
*testing
.T
) {
365 s
.mb
.msgs
[1] = &testMessage
{1, 3, false, "abc"}
366 s
.mb
.msgs
[2] = &testMessage
{2, 1, true, "Z"}
367 s
.mb
.msgs
[3] = &testMessage
{3, 4, false, "test"}
369 clientServerTest(t
, s
, []requestResponse
{
370 {"USER u", responseOK
},
371 {"PASS p", responseOK
},
372 {"UIDL", func(t testing
.TB
, tp
*textproto
.Conn
) string {
378 resp
, err
:= tp
.ReadDotLines()
385 fmt
.Sprintf("1 %p", s
.mb
.msgs
[1]),
386 fmt
.Sprintf("3 %p", s
.mb
.msgs
[3]),
388 if !reflect
.DeepEqual(resp
, want
) {
389 t
.Errorf("Want %v, got %v", want
, resp
)
394 {"QUIT", responseOK
},
398 func TestDele(t
*testing
.T
) {
400 s
.mb
.msgs
[1] = &testMessage
{1, 3, false, "abc"}
401 s
.mb
.msgs
[2] = &testMessage
{2, 1, false, "d"}
403 clientServerTest(t
, s
, []requestResponse
{
404 {"USER u", responseOK
},
405 {"PASS p", responseOK
},
406 {"STAT", expectOKResponse(func(s
string) bool {
407 return s
== "+OK 2 4"
409 {"DELE 1", responseOK
},
410 {"STAT", expectOKResponse(func(s
string) bool {
411 return s
== "+OK 1 1"
413 {"RSET", responseOK
},
414 {"STAT", expectOKResponse(func(s
string) bool {
415 return s
== "+OK 2 4"
417 {"QUIT", responseOK
},
420 if s
.mb
.msgs
[1].Deleted() || s
.mb
.msgs
[2].Deleted() {
421 t
.Errorf("RSET should not delete a message")
424 clientServerTest(t
, s
, []requestResponse
{
425 {"USER u", responseOK
},
426 {"PASS p", responseOK
},
427 {"STAT", expectOKResponse(func(s
string) bool {
428 return s
== "+OK 2 4"
430 {"DELE 1", responseOK
},
431 {"STAT", expectOKResponse(func(s
string) bool {
432 return s
== "+OK 1 1"
434 {"QUIT", responseOK
},
437 if !s
.mb
.msgs
[1].Deleted() {
438 t
.Errorf("DELE did not work")
440 if s
.mb
.msgs
[2].Deleted() {
441 t
.Errorf("DELE the wrong message")
445 func TestCapa(t
*testing
.T
) {
448 capaTest
:= func(t testing
.TB
, tp
*textproto
.Conn
) string {
454 resp
, err
:= tp
.ReadDotLines()
466 caps
:= map[string]int{
470 for _
, line
:= range resp
{
471 if val
, ok
:= caps
[line
]; ok
{
472 if val
== capNeeded
{
475 t
.Errorf("unxpected capa value %q", line
)
481 for c
, val
:= range caps
{
483 t
.Errorf("unexpected capa value for %q: %d", c
, val
)
489 clientServerTest(t
, s
, []requestResponse
{
491 {"USER u", responseOK
},
493 {"PASS p", responseOK
},
495 {"QUIT", responseOK
},