diff --git a/bindata.go b/bindata.go index b1e3966..2303d16 100644 --- a/bindata.go +++ b/bindata.go @@ -1594,17 +1594,17 @@ type _bintree_t struct { var _bintree = &_bintree_t{nil, map[string]*_bintree_t{ "assets": &_bintree_t{nil, map[string]*_bintree_t{ "images": &_bintree_t{nil, map[string]*_bintree_t{ - "ajax-loader.gif": &_bintree_t{assets_images_ajax_loader_gif, map[string]*_bintree_t{}}, "github.png": &_bintree_t{assets_images_github_png, map[string]*_bintree_t{}}, "hog.png": &_bintree_t{assets_images_hog_png, map[string]*_bintree_t{}}, + "ajax-loader.gif": &_bintree_t{assets_images_ajax_loader_gif, map[string]*_bintree_t{}}, }}, "js": &_bintree_t{nil, map[string]*_bintree_t{ "controllers.js": &_bintree_t{assets_js_controllers_js, map[string]*_bintree_t{}}, "strutil.js": &_bintree_t{assets_js_strutil_js, map[string]*_bintree_t{}}, }}, "templates": &_bintree_t{nil, map[string]*_bintree_t{ - "layout.html": &_bintree_t{assets_templates_layout_html, map[string]*_bintree_t{}}, "index.html": &_bintree_t{assets_templates_index_html, map[string]*_bintree_t{}}, + "layout.html": &_bintree_t{assets_templates_layout_html, map[string]*_bintree_t{}}, }}, }}, }} diff --git a/mailhog/smtp/protocol/protocol.go b/mailhog/smtp/protocol/protocol.go index fccd15e..87893c5 100644 --- a/mailhog/smtp/protocol/protocol.go +++ b/mailhog/smtp/protocol/protocol.go @@ -50,7 +50,7 @@ func NewProtocol() *Protocol { return &Protocol{ state: INVALID, message: &data.SMTPMessage{}, - Hostname: "", + Hostname: "mailhog.example", Ident: "ESMTP Go-MailHog", } } @@ -313,7 +313,7 @@ func ParseMAIL(mail string) (string, error) { r := regexp.MustCompile("(?i:From):<([^>]+)>") match := r.FindStringSubmatch(mail) if len(match) != 2 { - return "", errors.New("Invalid sender " + mail) + return "", errors.New("Invalid syntax in MAIL command") } return match[1], nil } @@ -323,7 +323,7 @@ func ParseRCPT(rcpt string) (string, error) { r := regexp.MustCompile("(?i:To):<([^>]+)>") match := r.FindStringSubmatch(rcpt) if len(match) != 2 { - return "", errors.New("Invalid recipient " + rcpt) + return "", errors.New("Invalid syntax in RCPT command") } return match[1], nil } diff --git a/mailhog/smtp/protocol/protocol_test.go b/mailhog/smtp/protocol/protocol_test.go new file mode 100644 index 0000000..41392aa --- /dev/null +++ b/mailhog/smtp/protocol/protocol_test.go @@ -0,0 +1,161 @@ +package protocol + +// http://www.rfc-editor.org/rfc/rfc5321.txt + +import ( + "testing" + + "github.com/ian-kent/Go-MailHog/mailhog/data" + . "github.com/smartystreets/goconvey/convey" +) + +func TestProtocol(t *testing.T) { + Convey("NewProtocol returns a new Protocol", t, func() { + proto := NewProtocol() + So(proto, ShouldNotBeNil) + So(proto, ShouldHaveSameTypeAs, &Protocol{}) + So(proto.Hostname, ShouldEqual, "mailhog.example") + So(proto.Ident, ShouldEqual, "ESMTP Go-MailHog") + So(proto.state, ShouldEqual, INVALID) + So(proto.message, ShouldNotBeNil) + So(proto.message, ShouldHaveSameTypeAs, &data.SMTPMessage{}) + }) + + Convey("Start should modify the state correctly", t, func() { + proto := NewProtocol() + So(proto.state, ShouldEqual, INVALID) + reply := proto.Start() + So(proto.state, ShouldEqual, ESTABLISH) + So(reply, ShouldNotBeNil) + So(reply, ShouldHaveSameTypeAs, &Reply{}) + So(reply.Status, ShouldEqual, 220) + So(reply.Lines(), ShouldResemble, []string{"220 mailhog.example ESMTP Go-MailHog\n"}) + }) + + Convey("Modifying the hostname should modify the ident reply", t, func() { + proto := NewProtocol() + proto.Ident = "OinkSMTP Go-MailHog" + reply := proto.Start() + So(reply, ShouldNotBeNil) + So(reply, ShouldHaveSameTypeAs, &Reply{}) + So(reply.Status, ShouldEqual, 220) + So(reply.Lines(), ShouldResemble, []string{"220 mailhog.example OinkSMTP Go-MailHog\n"}) + }) + + Convey("Modifying the ident should modify the ident reply", t, func() { + proto := NewProtocol() + proto.Hostname = "oink.oink" + reply := proto.Start() + So(reply, ShouldNotBeNil) + So(reply, ShouldHaveSameTypeAs, &Reply{}) + So(reply.Status, ShouldEqual, 220) + So(reply.Lines(), ShouldResemble, []string{"220 oink.oink ESMTP Go-MailHog\n"}) + }) +} + +func TestEHLO(t *testing.T) { + Convey("EHLO should modify the state correctly", t, func() { + proto := NewProtocol() + proto.Start() + So(proto.state, ShouldEqual, ESTABLISH) + So(proto.message.Helo, ShouldEqual, "") + reply := proto.EHLO("localhost") + So(reply, ShouldNotBeNil) + So(reply.Status, ShouldEqual, 250) + So(reply.Lines(), ShouldResemble, []string{"250-Hello localhost\n", "250-PIPELINING\n", "250 AUTH EXTERNAL CRAM-MD5 LOGIN PLAIN\n"}) + So(proto.state, ShouldEqual, MAIL) + So(proto.message.Helo, ShouldEqual, "localhost") + }) +} + +func TestHELO(t *testing.T) { + Convey("HELO should modify the state correctly", t, func() { + proto := NewProtocol() + proto.Start() + So(proto.state, ShouldEqual, ESTABLISH) + So(proto.message.Helo, ShouldEqual, "") + reply := proto.HELO("localhost") + So(reply, ShouldNotBeNil) + So(reply.Status, ShouldEqual, 250) + So(reply.Lines(), ShouldResemble, []string{"250 Hello localhost\n"}) + So(proto.state, ShouldEqual, MAIL) + So(proto.message.Helo, ShouldEqual, "localhost") + }) +} + +func TestRSET(t *testing.T) { + Convey("RSET should reset the state correctly", t, func() { + proto := NewProtocol() + proto.Start() + proto.HELO("localhost") + proto.Command(&Command{"MAIL", "FROM:"}) + proto.Command(&Command{"RCPT", "TO:"}) + So(proto.state, ShouldEqual, RCPT) + So(proto.message.From, ShouldEqual, "test") + So(len(proto.message.To), ShouldEqual, 1) + So(proto.message.To[0], ShouldEqual, "test") + reply := proto.Command(&Command{"RSET", ""}) + So(reply, ShouldNotBeNil) + So(reply.Status, ShouldEqual, 250) + So(reply.Lines(), ShouldResemble, []string{"250 Ok\n"}) + So(proto.state, ShouldEqual, MAIL) + So(proto.message.From, ShouldEqual, "") + So(len(proto.message.To), ShouldEqual, 0) + }) +} + +func TestParseMAIL(t *testing.T) { + Convey("ParseMAIL should parse MAIL command arguments", t, func() { + m, err := ParseMAIL("FROM:") + So(err, ShouldBeNil) + So(m, ShouldEqual, "oink@mailhog.example") + m, err = ParseMAIL("FROM:") + So(err, ShouldBeNil) + So(m, ShouldEqual, "oink") + }) + Convey("ParseMAIL should return an error for invalid syntax", t, func() { + m, err := ParseMAIL("FROM:oink") + So(err, ShouldNotBeNil) + So(err.Error(), ShouldEqual, "Invalid syntax in MAIL command") + So(m, ShouldEqual, "") + }) + Convey("ParseMAIL should be case-insensitive", t, func() { + m, err := ParseMAIL("FROM:") + So(err, ShouldBeNil) + So(m, ShouldEqual, "oink") + m, err = ParseMAIL("from:") + So(err, ShouldBeNil) + So(m, ShouldEqual, "oink@mailhog.example") + m, err = ParseMAIL("FrOm:") + So(err, ShouldBeNil) + So(m, ShouldEqual, "oink@oink.mailhog.example") + }) +} + +func TestParseRCPT(t *testing.T) { + Convey("ParseRCPT should parse RCPT command arguments", t, func() { + m, err := ParseRCPT("TO:") + So(err, ShouldBeNil) + So(m, ShouldEqual, "oink@mailhog.example") + m, err = ParseRCPT("TO:") + So(err, ShouldBeNil) + So(m, ShouldEqual, "oink") + }) + Convey("ParseRCPT should return an error for invalid syntax", t, func() { + m, err := ParseRCPT("TO:oink") + So(err, ShouldNotBeNil) + So(err.Error(), ShouldEqual, "Invalid syntax in RCPT command") + So(m, ShouldEqual, "") + }) + Convey("ParseRCPT should be case-insensitive", t, func() { + m, err := ParseRCPT("TO:") + So(err, ShouldBeNil) + So(m, ShouldEqual, "oink") + m, err = ParseRCPT("to:") + So(err, ShouldBeNil) + So(m, ShouldEqual, "oink@mailhog.example") + m, err = ParseRCPT("To:") + So(err, ShouldBeNil) + So(m, ShouldEqual, "oink@oink.mailhog.example") + }) +}