Add more protocol tests

This commit is contained in:
Ian Kent 2014-11-23 03:48:35 +00:00
parent 09098402f8
commit 33d706d401
3 changed files with 392 additions and 11 deletions

View file

@ -1593,11 +1593,6 @@ type _bintree_t struct {
var _bintree = &_bintree_t{nil, map[string]*_bintree_t{ var _bintree = &_bintree_t{nil, map[string]*_bintree_t{
"assets": &_bintree_t{nil, map[string]*_bintree_t{ "assets": &_bintree_t{nil, map[string]*_bintree_t{
"images": &_bintree_t{nil, 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{ "js": &_bintree_t{nil, map[string]*_bintree_t{
"controllers.js": &_bintree_t{assets_js_controllers_js, 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{}}, "strutil.js": &_bintree_t{assets_js_strutil_js, map[string]*_bintree_t{}},
@ -1606,5 +1601,10 @@ var _bintree = &_bintree_t{nil, map[string]*_bintree_t{
"index.html": &_bintree_t{assets_templates_index_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{}}, "layout.html": &_bintree_t{assets_templates_layout_html, 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{}},
}},
}}, }},
}} }}

View file

@ -87,12 +87,7 @@ func (proto *Protocol) Parse(line string) (string, *Reply) {
} }
parts := strings.SplitN(line, "\n", 2) parts := strings.SplitN(line, "\n", 2)
if len(parts) == 2 {
line = parts[1] line = parts[1]
} else {
line = ""
}
// TODO collapse AUTH states into separate processing // TODO collapse AUTH states into separate processing
if proto.state == DATA { if proto.state == DATA {
@ -280,6 +275,10 @@ func (proto *Protocol) Command(command *Command) (reply *Reply) {
proto.message.To = append(proto.message.To, to) proto.message.To = append(proto.message.To, to)
proto.state = RCPT proto.state = RCPT
return ReplyRecipientOk(to) return ReplyRecipientOk(to)
case "HELO":
return proto.HELO(command.args)
case "EHLO":
return proto.EHLO(command.args)
case "DATA": case "DATA":
proto.logf("Got DATA command, switching to DATA state") proto.logf("Got DATA command, switching to DATA state")
proto.state = DATA proto.state = DATA

View file

@ -22,6 +22,19 @@ func TestProtocol(t *testing.T) {
So(proto.message, ShouldHaveSameTypeAs, &data.SMTPMessage{}) So(proto.message, ShouldHaveSameTypeAs, &data.SMTPMessage{})
}) })
Convey("LogHandler should be called for logging", t, func() {
proto := NewProtocol()
handlerCalled := false
proto.LogHandler = func(message string, args ...interface{}) {
handlerCalled = true
So(message, ShouldEqual, "[PROTO: %s] Started session, switching to ESTABLISH state")
So(len(args), ShouldEqual, 1)
So(args[0], ShouldEqual, "INVALID")
}
proto.Start()
So(handlerCalled, ShouldBeTrue)
})
Convey("Start should modify the state correctly", t, func() { Convey("Start should modify the state correctly", t, func() {
proto := NewProtocol() proto := NewProtocol()
So(proto.state, ShouldEqual, INVALID) So(proto.state, ShouldEqual, INVALID)
@ -54,6 +67,194 @@ func TestProtocol(t *testing.T) {
}) })
} }
func TestProcessCommand(t *testing.T) {
Convey("ProcessCommand should attempt to process anything", t, func() {
proto := NewProtocol()
reply := proto.ProcessCommand("OINK mailhog.example")
So(proto.state, ShouldEqual, INVALID)
So(reply, ShouldNotBeNil)
So(reply.Status, ShouldEqual, 500)
So(reply.Lines(), ShouldResemble, []string{"500 Unrecognised command\n"})
proto.Start()
So(proto.state, ShouldEqual, ESTABLISH)
reply = proto.ProcessCommand("HELO localhost")
So(proto.state, ShouldEqual, MAIL)
So(reply, ShouldNotBeNil)
So(reply.Status, ShouldEqual, 250)
So(reply.Lines(), ShouldResemble, []string{"250 Hello localhost\n"})
reply = proto.ProcessCommand("OINK mailhog.example")
So(proto.state, ShouldEqual, MAIL)
So(reply, ShouldNotBeNil)
So(reply.Status, ShouldEqual, 500)
So(reply.Lines(), ShouldResemble, []string{"500 Unrecognised command\n"})
})
}
func TestParse(t *testing.T) {
Convey("Parse can parse partial and multiple commands", t, func() {
proto := NewProtocol()
proto.Start()
So(proto.state, ShouldEqual, ESTABLISH)
line, reply := proto.Parse("HELO localhost")
So(proto.state, ShouldEqual, ESTABLISH)
So(reply, ShouldBeNil)
So(line, ShouldEqual, "HELO localhost")
line, reply = proto.Parse("HELO localhost\nMAIL Fro")
So(proto.state, ShouldEqual, MAIL)
So(reply, ShouldNotBeNil)
So(line, ShouldEqual, "MAIL Fro")
line, reply = proto.Parse("MAIL From:<test>\n")
So(proto.state, ShouldEqual, RCPT)
So(reply, ShouldNotBeNil)
So(line, ShouldEqual, "")
})
Convey("Parse can call ProcessData", t, func() {
proto := NewProtocol()
proto.Start()
proto.Command(&Command{"EHLO", "localhost"})
proto.Command(&Command{"MAIL", "From:<test>"})
proto.Command(&Command{"RCPT", "To:<test>"})
proto.Command(&Command{"DATA", ""})
So(proto.state, ShouldEqual, DATA)
line, reply := proto.Parse("Hi\n")
So(proto.state, ShouldEqual, DATA)
So(line, ShouldEqual, "")
So(proto.message.Data, ShouldEqual, "Hi\n")
So(reply, ShouldBeNil)
line, reply = proto.Parse("\r\n")
So(proto.state, ShouldEqual, DATA)
So(line, ShouldEqual, "")
So(proto.message.Data, ShouldEqual, "Hi\n\r\n")
So(reply, ShouldBeNil)
line, reply = proto.Parse(".\r\n")
So(proto.state, ShouldEqual, MAIL)
So(line, ShouldEqual, "")
So(reply, ShouldNotBeNil)
So(proto.message.Data, ShouldEqual, "Hi\n")
})
}
func TestUnknownCommands(t *testing.T) {
Convey("Unknown command in INVALID state", t, func() {
proto := NewProtocol()
So(proto.state, ShouldEqual, INVALID)
reply := proto.Command(&Command{"OINK", ""})
So(reply, ShouldNotBeNil)
So(reply.Status, ShouldEqual, 500)
So(reply.Lines(), ShouldResemble, []string{"500 Unrecognised command\n"})
})
Convey("Unknown command in ESTABLISH state", t, func() {
proto := NewProtocol()
proto.Start()
So(proto.state, ShouldEqual, ESTABLISH)
reply := proto.Command(&Command{"OINK", ""})
So(reply, ShouldNotBeNil)
So(reply.Status, ShouldEqual, 500)
So(reply.Lines(), ShouldResemble, []string{"500 Unrecognised command\n"})
})
Convey("Unknown command in MAIL state", t, func() {
proto := NewProtocol()
proto.Start()
proto.Command(&Command{"EHLO", "localhost"})
So(proto.state, ShouldEqual, MAIL)
reply := proto.Command(&Command{"OINK", ""})
So(reply, ShouldNotBeNil)
So(reply.Status, ShouldEqual, 500)
So(reply.Lines(), ShouldResemble, []string{"500 Unrecognised command\n"})
})
Convey("Unknown command in RCPT state", t, func() {
proto := NewProtocol()
proto.Start()
proto.Command(&Command{"EHLO", "localhost"})
proto.Command(&Command{"MAIL", "FROM:<test>"})
So(proto.state, ShouldEqual, RCPT)
reply := proto.Command(&Command{"OINK", ""})
So(reply, ShouldNotBeNil)
So(reply.Status, ShouldEqual, 500)
So(reply.Lines(), ShouldResemble, []string{"500 Unrecognised command\n"})
})
}
func TestESTABLISHCommands(t *testing.T) {
Convey("EHLO should work in ESTABLISH state", t, func() {
proto := NewProtocol()
proto.Start()
So(proto.state, ShouldEqual, ESTABLISH)
reply := proto.Command(&Command{"EHLO", "localhost"})
So(reply, ShouldNotBeNil)
So(reply.Status, ShouldEqual, 250)
})
Convey("HELO should work in ESTABLISH state", t, func() {
proto := NewProtocol()
proto.Start()
So(proto.state, ShouldEqual, ESTABLISH)
reply := proto.Command(&Command{"HELO", "localhost"})
So(reply, ShouldNotBeNil)
So(reply.Status, ShouldEqual, 250)
})
Convey("RSET should work in ESTABLISH state", t, func() {
proto := NewProtocol()
proto.Start()
So(proto.state, ShouldEqual, ESTABLISH)
reply := proto.Command(&Command{"RSET", ""})
So(reply, ShouldNotBeNil)
So(reply.Status, ShouldEqual, 250)
})
Convey("NOOP should work in ESTABLISH state", t, func() {
proto := NewProtocol()
proto.Start()
So(proto.state, ShouldEqual, ESTABLISH)
reply := proto.Command(&Command{"NOOP", ""})
So(reply, ShouldNotBeNil)
So(reply.Status, ShouldEqual, 250)
})
Convey("QUIT should work in ESTABLISH state", t, func() {
proto := NewProtocol()
proto.Start()
So(proto.state, ShouldEqual, ESTABLISH)
reply := proto.Command(&Command{"QUIT", ""})
So(reply, ShouldNotBeNil)
So(reply.Status, ShouldEqual, 221)
})
Convey("MAIL shouldn't work in ESTABLISH state", t, func() {
proto := NewProtocol()
proto.Start()
So(proto.state, ShouldEqual, ESTABLISH)
reply := proto.Command(&Command{"MAIL", ""})
So(reply, ShouldNotBeNil)
So(reply.Status, ShouldEqual, 500)
So(reply.Lines(), ShouldResemble, []string{"500 Unrecognised command\n"})
})
Convey("RCPT shouldn't work in ESTABLISH state", t, func() {
proto := NewProtocol()
proto.Start()
So(proto.state, ShouldEqual, ESTABLISH)
reply := proto.Command(&Command{"RCPT", ""})
So(reply, ShouldNotBeNil)
So(reply.Status, ShouldEqual, 500)
So(reply.Lines(), ShouldResemble, []string{"500 Unrecognised command\n"})
})
Convey("DATA shouldn't work in ESTABLISH state", t, func() {
proto := NewProtocol()
proto.Start()
So(proto.state, ShouldEqual, ESTABLISH)
reply := proto.Command(&Command{"DATA", ""})
So(reply, ShouldNotBeNil)
So(reply.Status, ShouldEqual, 500)
So(reply.Lines(), ShouldResemble, []string{"500 Unrecognised command\n"})
})
}
func TestEHLO(t *testing.T) { func TestEHLO(t *testing.T) {
Convey("EHLO should modify the state correctly", t, func() { Convey("EHLO should modify the state correctly", t, func() {
proto := NewProtocol() proto := NewProtocol()
@ -67,6 +268,45 @@ func TestEHLO(t *testing.T) {
So(proto.state, ShouldEqual, MAIL) So(proto.state, ShouldEqual, MAIL)
So(proto.message.Helo, ShouldEqual, "localhost") So(proto.message.Helo, ShouldEqual, "localhost")
}) })
Convey("EHLO should work using Command", t, func() {
proto := NewProtocol()
proto.Start()
So(proto.state, ShouldEqual, ESTABLISH)
So(proto.message.Helo, ShouldEqual, "")
reply := proto.Command(&Command{"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")
})
Convey("HELO should work in MAIL state", t, func() {
proto := NewProtocol()
proto.Start()
proto.Command(&Command{"HELO", "localhost"})
So(proto.state, ShouldEqual, MAIL)
So(proto.message.Helo, ShouldEqual, "localhost")
reply := proto.Command(&Command{"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")
})
Convey("HELO should work in RCPT state", t, func() {
proto := NewProtocol()
proto.Start()
proto.Command(&Command{"HELO", "localhost"})
proto.Command(&Command{"MAIL", "From:<test>"})
So(proto.state, ShouldEqual, RCPT)
So(proto.message.Helo, ShouldEqual, "localhost")
reply := proto.Command(&Command{"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) { func TestHELO(t *testing.T) {
@ -82,12 +322,53 @@ func TestHELO(t *testing.T) {
So(proto.state, ShouldEqual, MAIL) So(proto.state, ShouldEqual, MAIL)
So(proto.message.Helo, ShouldEqual, "localhost") So(proto.message.Helo, ShouldEqual, "localhost")
}) })
Convey("HELO should work using Command", t, func() {
proto := NewProtocol()
proto.Start()
So(proto.state, ShouldEqual, ESTABLISH)
So(proto.message.Helo, ShouldEqual, "")
reply := proto.Command(&Command{"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")
})
Convey("HELO should work in MAIL state", t, func() {
proto := NewProtocol()
proto.Start()
proto.Command(&Command{"HELO", "localhost"})
So(proto.state, ShouldEqual, MAIL)
So(proto.message.Helo, ShouldEqual, "localhost")
reply := proto.Command(&Command{"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")
})
Convey("HELO should work in RCPT state", t, func() {
proto := NewProtocol()
proto.Start()
proto.Command(&Command{"HELO", "localhost"})
proto.Command(&Command{"MAIL", "From:<test>"})
So(proto.state, ShouldEqual, RCPT)
So(proto.message.Helo, ShouldEqual, "localhost")
reply := proto.Command(&Command{"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 TestDATA(t *testing.T) { func TestDATA(t *testing.T) {
Convey("DATA should accept data", t, func() { Convey("DATA should accept data", t, func() {
proto := NewProtocol() proto := NewProtocol()
handlerCalled := false
proto.MessageReceivedHandler = func(msg *data.Message) (string, error) { proto.MessageReceivedHandler = func(msg *data.Message) (string, error) {
handlerCalled = true
return "abc", nil return "abc", nil
} }
proto.Start() proto.Start()
@ -112,6 +393,7 @@ func TestDATA(t *testing.T) {
So(reply.Status, ShouldEqual, 250) So(reply.Status, ShouldEqual, 250)
So(proto.state, ShouldEqual, MAIL) So(proto.state, ShouldEqual, MAIL)
So(reply.Lines(), ShouldResemble, []string{"250 Ok: queued as abc\n"}) So(reply.Lines(), ShouldResemble, []string{"250 Ok: queued as abc\n"})
So(handlerCalled, ShouldBeTrue)
}) })
Convey("Should return error if missing storage backend", t, func() { Convey("Should return error if missing storage backend", t, func() {
proto := NewProtocol() proto := NewProtocol()
@ -140,7 +422,9 @@ func TestDATA(t *testing.T) {
}) })
Convey("Should return error if storage backend fails", t, func() { Convey("Should return error if storage backend fails", t, func() {
proto := NewProtocol() proto := NewProtocol()
handlerCalled := false
proto.MessageReceivedHandler = func(msg *data.Message) (string, error) { proto.MessageReceivedHandler = func(msg *data.Message) (string, error) {
handlerCalled = true
return "", errors.New("abc") return "", errors.New("abc")
} }
proto.Start() proto.Start()
@ -165,6 +449,7 @@ func TestDATA(t *testing.T) {
So(reply.Status, ShouldEqual, 452) So(reply.Status, ShouldEqual, 452)
So(proto.state, ShouldEqual, MAIL) So(proto.state, ShouldEqual, MAIL)
So(reply.Lines(), ShouldResemble, []string{"452 Unable to store message\n"}) So(reply.Lines(), ShouldResemble, []string{"452 Unable to store message\n"})
So(handlerCalled, ShouldBeTrue)
}) })
} }
@ -249,6 +534,53 @@ func TestParseMAIL(t *testing.T) {
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(m, ShouldEqual, "oink@oink.mailhog.example") So(m, ShouldEqual, "oink@oink.mailhog.example")
}) })
Convey("Error should be returned via Command", t, func() {
proto := NewProtocol()
proto.Start()
proto.Command(&Command{"HELO", "localhost"})
So(proto.state, ShouldEqual, MAIL)
reply := proto.Command(&Command{"MAIL", "oink"})
So(reply, ShouldNotBeNil)
So(reply.Status, ShouldEqual, 550)
So(reply.Lines(), ShouldResemble, []string{"550 Invalid syntax in MAIL command\n"})
So(proto.state, ShouldEqual, MAIL)
})
Convey("ValidateSenderHandler should be called", t, func() {
proto := NewProtocol()
handlerCalled := false
proto.ValidateSenderHandler = func(sender string) bool {
handlerCalled = true
So(sender, ShouldEqual, "oink@mailhog.example")
return true
}
proto.Start()
proto.Command(&Command{"HELO", "localhost"})
So(proto.state, ShouldEqual, MAIL)
reply := proto.Command(&Command{"MAIL", "From:<oink@mailhog.example>"})
So(handlerCalled, ShouldBeTrue)
So(reply, ShouldNotBeNil)
So(reply.Status, ShouldEqual, 250)
So(reply.Lines(), ShouldResemble, []string{"250 Sender oink@mailhog.example ok\n"})
So(proto.state, ShouldEqual, RCPT)
})
Convey("ValidateSenderHandler errors should be returned", t, func() {
proto := NewProtocol()
handlerCalled := false
proto.ValidateSenderHandler = func(sender string) bool {
handlerCalled = true
So(sender, ShouldEqual, "oink@mailhog.example")
return false
}
proto.Start()
proto.Command(&Command{"HELO", "localhost"})
So(proto.state, ShouldEqual, MAIL)
reply := proto.Command(&Command{"MAIL", "From:<oink@mailhog.example>"})
So(handlerCalled, ShouldBeTrue)
So(reply, ShouldNotBeNil)
So(reply.Status, ShouldEqual, 550)
So(reply.Lines(), ShouldResemble, []string{"550 Invalid sender oink@mailhog.example\n"})
So(proto.state, ShouldEqual, MAIL)
})
} }
func TestParseRCPT(t *testing.T) { func TestParseRCPT(t *testing.T) {
@ -277,4 +609,54 @@ func TestParseRCPT(t *testing.T) {
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(m, ShouldEqual, "oink@oink.mailhog.example") So(m, ShouldEqual, "oink@oink.mailhog.example")
}) })
Convey("Error should be returned via Command", t, func() {
proto := NewProtocol()
proto.Start()
proto.Command(&Command{"HELO", "localhost"})
proto.Command(&Command{"MAIL", "FROM:<test>"})
So(proto.state, ShouldEqual, RCPT)
reply := proto.Command(&Command{"RCPT", "oink"})
So(reply, ShouldNotBeNil)
So(reply.Status, ShouldEqual, 550)
So(reply.Lines(), ShouldResemble, []string{"550 Invalid syntax in RCPT command\n"})
So(proto.state, ShouldEqual, RCPT)
})
Convey("ValidateRecipientHandler should be called", t, func() {
proto := NewProtocol()
handlerCalled := false
proto.ValidateRecipientHandler = func(recipient string) bool {
handlerCalled = true
So(recipient, ShouldEqual, "oink@mailhog.example")
return true
}
proto.Start()
proto.Command(&Command{"HELO", "localhost"})
proto.Command(&Command{"MAIL", "FROM:<test>"})
So(proto.state, ShouldEqual, RCPT)
reply := proto.Command(&Command{"RCPT", "To:<oink@mailhog.example>"})
So(handlerCalled, ShouldBeTrue)
So(reply, ShouldNotBeNil)
So(reply.Status, ShouldEqual, 250)
So(reply.Lines(), ShouldResemble, []string{"250 Recipient oink@mailhog.example ok\n"})
So(proto.state, ShouldEqual, RCPT)
})
Convey("ValidateRecipientHandler errors should be returned", t, func() {
proto := NewProtocol()
handlerCalled := false
proto.ValidateRecipientHandler = func(recipient string) bool {
handlerCalled = true
So(recipient, ShouldEqual, "oink@mailhog.example")
return false
}
proto.Start()
proto.Command(&Command{"HELO", "localhost"})
proto.Command(&Command{"MAIL", "FROM:<test>"})
So(proto.state, ShouldEqual, RCPT)
reply := proto.Command(&Command{"RCPT", "To:<oink@mailhog.example>"})
So(handlerCalled, ShouldBeTrue)
So(reply, ShouldNotBeNil)
So(reply.Status, ShouldEqual, 550)
So(reply.Lines(), ShouldResemble, []string{"550 Invalid recipient oink@mailhog.example\n"})
So(proto.state, ShouldEqual, RCPT)
})
} }