diff --git a/pop3.go b/pop3.go index 8c2b563..5440dfc 100644 --- a/pop3.go +++ b/pop3.go @@ -113,7 +113,7 @@ func (c *Client) ListAll() ([]MessageInfo, error) { }) if err != nil { - return nil, ResponseError(fmt.Sprintf("LISTコマンドのレスポンスが不正です。: %s", err.Error())) + return nil, err } return list, nil @@ -144,7 +144,7 @@ func (c *Client) Uidl(number int) (int, string, error) { val, uid, err = c.convertNumberAndUid(msg) if err != nil { - return 0, "", ResponseError(fmt.Sprintf("UIDLコマンドのレスポンスが不正です。: %s", err.Error())) + return 0, "", err } return val, uid, nil @@ -168,7 +168,7 @@ func (c *Client) UidlAll() ([]MessageInfo, error) { }) if err != nil { - return nil, ResponseError(fmt.Sprintf("UIDLコマンドのレスポンスが不正です。: %s", err.Error())) + return nil, err } return list, nil @@ -232,7 +232,7 @@ func (c *Client) cmdStatOrList(name, format string, args ...interface{}) (int, u s := strings.Split(msg, " ") if len(s) < 2 { - return 0, 0, ResponseError(fmt.Sprintf("%sコマンドのレスポンスが不正です。: %s", name, msg)) + return 0, 0, ResponseError(fmt.Sprintf("invalid response format: %s", msg)) } var val int @@ -241,7 +241,7 @@ func (c *Client) cmdStatOrList(name, format string, args ...interface{}) (int, u val, size, err = c.convertNumberAndSize(msg) if err != nil { - return 0, 0, ResponseError(fmt.Sprintf("%sコマンドのレスポンスが不正です。: %s", name, err.Error())) + return 0, 0, err } return val, size, nil @@ -293,18 +293,18 @@ func (c *Client) convertNumberAndSize(line string) (int, uint64, error) { s := strings.Split(line, " ") if len(s) < 2 { - return 0, 0, errors.New(fmt.Sprintf("分割後の配列数が2未満です。: %s", line)) + return 0, 0, errors.New(fmt.Sprintf("the length of the array is less than 2: %s", line)) } var val int var size uint64 if val, err = strconv.Atoi(s[0]); err != nil { - return 0, 0, errors.New(fmt.Sprintf("配列要素[0]をint型に変換できません。: %s", line)) + return 0, 0, errors.New(fmt.Sprintf("can not convert element[0] to int type: %s", line)) } if size, err = strconv.ParseUint(s[1], 10, 64); err != nil { - return 0, 0, errors.New(fmt.Sprintf("配列要素[1]をuint64型に変換できません。: %s", line)) + return 0, 0, errors.New(fmt.Sprintf("can not convert element[1] to uint64 type: %s", line)) } return val, size, nil @@ -316,13 +316,13 @@ func (c *Client) convertNumberAndUid(line string) (int, string, error) { s := strings.Split(line, " ") if len(s) < 2 { - return 0, "", errors.New(fmt.Sprintf("分割後の配列数が2未満です。: %s", line)) + return 0, "", errors.New(fmt.Sprintf("the length of the array is less than 2: %s", line)) } var val int if val, err = strconv.Atoi(s[0]); err != nil { - return 0, "", errors.New(fmt.Sprintf("配列要素[0]をint型に変換できません。: %s", line)) + return 0, "", errors.New(fmt.Sprintf("can not convert element[0] to int type: %s", line)) } return val, s[1], nil diff --git a/pop3_test.go b/pop3_test.go new file mode 100644 index 0000000..0e6858e --- /dev/null +++ b/pop3_test.go @@ -0,0 +1,302 @@ +package pop3 + +import ( + "bufio" + "bytes" + "io" + "net" + "strings" + "testing" + "time" +) + +func TestSingleLineResponse(t *testing.T) { + if len(helloServer) != len(helloClient) { + t.Fatalf("Hello server and client size mismatch") + } + + for i := 0; i < len(helloServer); i++ { + execute(t, helloServer[i], helloClient[i], func(t *testing.T, c *Client) { + var err error + + switch i { + case 0: + err = c.User("testuser") + case 1: + err = c.Pass("testpassword") + case 2: + var count int + var size uint64 + count, size, err = c.Stat() + + if err == nil { + if count != 6 { + t.Errorf("Stat count:\nexpected:\n[%d]\nactual:\n[%d]\n", 6, count) + } + if size != 78680 { + t.Errorf("Stat size:\nexpected:\n[%d]\nactual:\n[%d]\n", 78680, count) + } + } + case 3: + //_, err = c.Retr(1) + case 4: + var number int + var size uint64 + number, size, err = c.List(1) + + if err == nil { + if number != 1 { + t.Errorf("List number:\nexpected:\n[%d]\nactual:\n[%d]\n", 1, number) + } + if size != 4404 { + t.Errorf("List size:\nexpected:\n[%d]\nactual:\n[%d]\n", 4404, size) + } + } + case 5: + //_, err = c.ListAll() + case 6: + var number int + var uid string + number, uid, err = c.Uidl(1) + + if err == nil { + if number != 1 { + t.Errorf("Uidl number:\nexpected:\n[%d]\nactual:\n[%d]\n", 1, number) + } + if uid != "DJzjbtr5hb2Lefq5Ass6eMjtEBV" { + t.Errorf("Uidl uid:\nexpected:\n[%s]\nactual:\n[%s]\n", "DJzjbtr5hb2Lefq5Ass6eMjtEBV", uid) + } + } + case 7: + //_, err = c.UidlAll() + case 8: + err = c.Dele(1) + case 9: + err = c.Noop() + case 10: + err = c.Rset() + case 11: + err = c.Quit() + default: + t.Fatalf("Unhandled command") + } + + if err != nil { + t.Errorf("Command %d failed: %v", i, err) + } + }) + } +} + +func TestRetr(t *testing.T) { + execute(t, retrServer, retrClient, func(t *testing.T, c *Client) { + var err error + var content string + + content, err = c.Retr(1) + + if err != nil { + t.Fatalf("Command failed: %v", err) + } + + expected := `Date: Sat, 15 Feb 2014 13:49:28 +0900 (JST) +From: +Subject: test mail +To: to@example.com +MIME-Version: 1.0 +Content-Type: text/plain; charset=utf-8 +Content-Transfer-Encoding: quoted-printable + +This is a test mail.` + + expected = strings.Replace(expected, "\n", "\r\n", -1) + + if content != expected { + t.Fatalf("excepted:\n[%s]\nactual:\n[%s]\n", expected, content) + } + }) +} + +func TestListAll(t *testing.T) { + execute(t, listAllServer, listAllClient, func(t *testing.T, c *Client) { + var err error + + exceptedMis := []MessageInfo{ + MessageInfo{Number: 1, Size: 4404}, + MessageInfo{Number: 2, Size: 3921}, + MessageInfo{Number: 3, Size: 6646}, + MessageInfo{Number: 4, Size: 55344}, + MessageInfo{Number: 5, Size: 5376}, + MessageInfo{Number: 6, Size: 2989}, + } + + var actualMis []MessageInfo + + actualMis, err = c.ListAll() + + if err != nil { + t.Fatalf("Command failed: %v", err) + } + + for i, mi := range actualMis { + if mi.Number != exceptedMis[i].Number { + t.Fatalf("%d: Number\nexcepted:\n[%d]\nactual:\n[%d]\n", i, exceptedMis[i].Number, mi.Number) + } + + if mi.Size != exceptedMis[i].Size { + t.Fatalf("%d: Size\nexcepted:\n[%d]\nactual:\n[%d]\n", i, exceptedMis[i].Size, mi.Size) + } + } + }) +} + +func TestUidlAll(t *testing.T) { + execute(t, uidlAllServer, uidlAllClient, func(t *testing.T, c *Client) { + var err error + + exceptedMis := []MessageInfo{ + MessageInfo{Number: 1, Uid: "DJzjbtr5hb2Lefq5Ass6eMjtEBV"}, + MessageInfo{Number: 2, Uid: "IeHR1LtUfd5MI1gz1sYOU4TG1rx"}, + MessageInfo{Number: 3, Uid: "Gjx3lbdNAnIMCJCuQxL05DFCqyy"}, + MessageInfo{Number: 4, Uid: "DANJyGBstEYirvbBVFXSh3CGXAg"}, + MessageInfo{Number: 5, Uid: "uhNSJrPJBlcacoEbX9aXMUKC90n"}, + MessageInfo{Number: 6, Uid: "fZ88gZjY2NdYOQA6aij8dxCCifC"}, + } + + var actualMis []MessageInfo + + actualMis, err = c.UidlAll() + + if err != nil { + t.Errorf("Command failed: %v", err) + } + + for i, mi := range actualMis { + if mi.Number != exceptedMis[i].Number { + t.Fatalf("%d: Number\nexcepted:\n[%d]\nactual:\n[%d]\n", i, exceptedMis[i].Number, mi.Number) + } + + if mi.Uid != exceptedMis[i].Uid { + t.Fatalf("%d: Uid\nexcepted:\n[%s]\nactual:\n[%s]\n", i, exceptedMis[i].Uid, mi.Uid) + } + } + }) +} + +func execute(t *testing.T, sServer, sClient string, processFn processFunc) { + server := strings.Join(strings.Split(baseHelloServer+sServer, "\n"), "\r\n") + client := strings.Join(strings.Split(baseHelloClient+sClient, "\n"), "\r\n") + + var cmdbuf bytes.Buffer + + bcmdbuf := bufio.NewWriter(&cmdbuf) + + var fake faker + fake.ReadWriter = bufio.NewReadWriter(bufio.NewReader(strings.NewReader(server)), bcmdbuf) + + c, err := NewClient(fake) + + if err != nil { + t.Fatalf("NewClient: %v", err) + } + + defer c.Close() + + processFn(t, c) + + bcmdbuf.Flush() + + actualcmds := cmdbuf.String() + + if client != actualcmds { + t.Errorf("Got:\n[%s]\nExpected:\n[%s]", actualcmds, client) + } +} + +type processFunc func(t *testing.T, c *Client) + +var baseHelloServer = `+OK hello from popgate(2.35.25) +` + +var helloServer = []string{ + "+OK password required.\n", + "+OK maildrop ready, 6 messages (78680 octets)\n", + "+OK 6 78680\n", + "+OK 4404 octets\n", + "+OK 1 4404\n", + "+OK 6 messages (78680 octets)\n", + "+OK 1 DJzjbtr5hb2Lefq5Ass6eMjtEBV\n", + "+OK 6 messages (78680 octets)\n", + "+OK message 1 marked deleted\n", + "+OK \n", + "+OK maildrop has 0 messages (0 octets)\n", + "+OK server signing off.\n", +} + +var baseHelloClient = `` + +var helloClient = []string{ + "USER testuser\n", + "PASS testpassword\n", + "STAT\n", + "", //"RETR 1\n", + "LIST 1\n", + "", //"LIST\n", + "UIDL 1\n", + "", //"UIDL\n", + "DELE 1\n", + "NOOP\n", + "RSET\n", + "QUIT\n", +} + +var retrServer = `+OK 3970 octets +Date: Sat, 15 Feb 2014 13:49:28 +0900 (JST) +From: +Subject: test mail +To: to@example.com +MIME-Version: 1.0 +Content-Type: text/plain; charset=utf-8 +Content-Transfer-Encoding: quoted-printable + +This is a test mail. +. +` + +var retrClient = `RETR 1 +` + +var listAllServer = `+OK 6 messages (78680 octets) +1 4404 +2 3921 +3 6646 +4 55344 +5 5376 +6 2989 +. +` +var listAllClient = `LIST +` + +var uidlAllServer = `+OK 6 messages (78680 octets) +1 DJzjbtr5hb2Lefq5Ass6eMjtEBV +2 IeHR1LtUfd5MI1gz1sYOU4TG1rx +3 Gjx3lbdNAnIMCJCuQxL05DFCqyy +4 DANJyGBstEYirvbBVFXSh3CGXAg +5 uhNSJrPJBlcacoEbX9aXMUKC90n +6 fZ88gZjY2NdYOQA6aij8dxCCifC +. +` +var uidlAllClient = `UIDL +` + +type faker struct { + io.ReadWriter +} + +func (f faker) Close() error { return nil } +func (f faker) LocalAddr() net.Addr { return nil } +func (f faker) RemoteAddr() net.Addr { return nil } +func (f faker) SetDeadline(time.Time) error { return nil } +func (f faker) SetReadDeadline(time.Time) error { return nil } +func (f faker) SetWriteDeadline(time.Time) error { return nil } diff --git a/pop3proto.go b/pop3proto.go index ecbb56b..763f8f2 100644 --- a/pop3proto.go +++ b/pop3proto.go @@ -73,7 +73,7 @@ func (r *Reader) ReadToPeriod() (string, error) { lines, err := r.ReadLines() if err != nil { - return "", nil + return "", err } return strings.Join(lines, "\r\n"), nil diff --git a/pop3proto_test.go b/pop3proto_test.go new file mode 100644 index 0000000..65d2ae2 --- /dev/null +++ b/pop3proto_test.go @@ -0,0 +1,95 @@ +package pop3 + +import ( + "bufio" + "bytes" + "io" + "strings" + "testing" +) + +func TestReadLine(t *testing.T) { + // This function calls textproto.Reader.ReadLine only. + // so no need for test. +} + +func TestReadLines(t *testing.T) { + r := reader("line1\nline2\n.\n") + + s, err := r.ReadLines() + + if len(s) != 2 || s[0] != "line1" || s[1] != "line2" || err != nil { + t.Fatalf("%v, %v", s, err) + } + + s, err = r.ReadLines() + + if s != nil || err != io.EOF { + t.Fatalf("EOF: %s, %v", s, err) + } +} + +func TestReadToPeriod(t *testing.T) { + r := reader("line1\nline2\n.\n") + + s, err := r.ReadToPeriod() + + expected := strings.Replace(`line1 +line2`, "\n", "\r\n", -1) + + if s != expected || err != nil { + t.Fatalf("%v, %v", s, err) + } + + s, err = r.ReadToPeriod() + + if s != "" || err != io.EOF { + t.Fatalf("EOF: %s, %v", s, err) + } +} + +func TestReadResponse(t *testing.T) { + var r *Reader + var s string + var err error + + r = reader("+OK message\n") + s, err = r.ReadResponse() + if s != "message" || err != nil { + t.Fatalf("%v, %v", s, err) + } + + r = reader("-ERR message\n") + s, err = r.ReadResponse() + if s != "" || err == nil || err.Error() != "message" { + t.Fatalf("%v, %v", s, err) + } + + r = reader("message\n") + s, err = r.ReadResponse() + if s != "" || err == nil || err.Error() != "レスポンスのフォーマットが不正です。: message" { + t.Fatalf("%v, %v", s, err) + } + + r = reader("* message\n") + s, err = r.ReadResponse() + if s != "" || err == nil || err.Error() != "レスポンスの内容が不明です。: * message" { + t.Fatalf("%v, %v", s, err) + } +} + +func TestWriteLine(t *testing.T) { + var buf bytes.Buffer + + w := NewWriter(bufio.NewWriter(&buf)) + + err := w.WriteLine("foo %d", 123) + + if s := buf.String(); s != "foo 123\r\n" || err != nil { + t.Fatalf("s=%q; err=%s", s, err) + } +} + +func reader(s string) *Reader { + return NewReader(bufio.NewReader(strings.NewReader(s))) +}