diff --git a/etc/mailbox-list.sh b/etc/mailbox-list.sh index da5ca4c..fa0cb78 100755 --- a/etc/mailbox-list.sh +++ b/etc/mailbox-list.sh @@ -1 +1 @@ -curl -i -H "Accept: application/json" --noproxy localhost http://localhost:9000/mailbox/$1 +curl -i -H "Accept: application/json" --noproxy localhost "http://localhost:9000/mailbox/$1" diff --git a/inbucket.go b/inbucket.go index ddec725..c1f0022 100644 --- a/inbucket.go +++ b/inbucket.go @@ -98,6 +98,7 @@ func main() { ds := smtpd.DefaultFileDataStore() // Start HTTP server + web.Initialize(config.GetWebConfig(), ds) go web.Start() // Start POP3 server diff --git a/web/rest_test.go b/web/rest_test.go new file mode 100644 index 0000000..057b439 --- /dev/null +++ b/web/rest_test.go @@ -0,0 +1,195 @@ +package web + +import ( + "bytes" + "github.com/jhillyerd/go.enmime" + "github.com/jhillyerd/inbucket/config" + "github.com/jhillyerd/inbucket/smtpd" + "github.com/stretchr/testify/mock" + "io" + "log" + "net/http" + "net/http/httptest" + "net/mail" + "os" + "testing" + "time" +) + +func TestRestMailboxList(t *testing.T) { + // Create Mock Objects + ds := &MockDataStore{} + emptybox := &MockMailbox{} + ds.On("MailboxFor", "empty").Return(emptybox, nil) + emptybox.On("GetMessages").Return([]smtpd.Message{}, nil) + + logbuf := setupWebServer(ds) + + // Test invalid mailbox name + w, err := testRestGet("http://localhost/mailbox/foo@bar") + expectCode := 500 + if err != nil { + t.Fatal(err) + } + if w.Code != expectCode { + t.Errorf("Expected code %v, got %v", expectCode, w.Code) + } + + // Test empty mailbox + w, err = testRestGet("http://localhost/mailbox/empty") + expectCode = 200 + if err != nil { + t.Fatal(err) + } + if w.Code != expectCode { + t.Errorf("Expected code %v, got %v", expectCode, w.Code) + } + + if t.Failed() { + // Wait for handler to finish logging + time.Sleep(2 * time.Second) + // Dump buffered log data if there was a failure + io.Copy(os.Stderr, logbuf) + } +} + +func testRestGet(url string) (*httptest.ResponseRecorder, error) { + req, err := http.NewRequest("GET", url, nil) + req.Header.Add("Accept", "application/json") + if err != nil { + return nil, err + } + + w := httptest.NewRecorder() + Router.ServeHTTP(w, req) + return w, nil +} + +func setupWebServer(ds smtpd.DataStore) *bytes.Buffer { + // Capture log output + buf := new(bytes.Buffer) + log.SetOutput(buf) + + cfg := config.WebConfig{ + TemplateDir: "../themes/integral/templates", + PublicDir: "../themes/integral/public", + } + Initialize(cfg, ds) + + return buf +} + +// Mock DataStore object +type MockDataStore struct { + mock.Mock +} + +func (m *MockDataStore) MailboxFor(name string) (smtpd.Mailbox, error) { + args := m.Called(name) + return args.Get(0).(smtpd.Mailbox), args.Error(1) +} + +func (m *MockDataStore) AllMailboxes() ([]smtpd.Mailbox, error) { + args := m.Called() + return args.Get(0).([]smtpd.Mailbox), args.Error(1) +} + +// Mock Mailbox object +type MockMailbox struct { + mock.Mock +} + +func (m *MockMailbox) GetMessages() ([]smtpd.Message, error) { + args := m.Called() + return args.Get(0).([]smtpd.Message), args.Error(1) +} + +func (m *MockMailbox) GetMessage(id string) (smtpd.Message, error) { + args := m.Called(id) + return args.Get(0).(smtpd.Message), args.Error(1) +} + +func (m *MockMailbox) Purge() error { + args := m.Called() + return args.Error(0) +} + +func (m *MockMailbox) NewMessage() smtpd.Message { + args := m.Called() + return args.Get(0).(smtpd.Message) +} + +func (m *MockMailbox) String() string { + args := m.Called() + return args.String(0) +} + +// Mock Message object +type MockMessage struct { + mock.Mock +} + +func (m *MockMessage) Id() string { + args := m.Called() + return args.String(0) +} + +func (m *MockMessage) From() string { + args := m.Called() + return args.String(0) +} + +func (m *MockMessage) Date() time.Time { + args := m.Called() + return args.Get(0).(time.Time) +} + +func (m *MockMessage) Subject() string { + args := m.Called() + return args.String(0) +} + +func (m *MockMessage) ReadHeader() (msg *mail.Message, err error) { + args := m.Called() + return args.Get(0).(*mail.Message), args.Error(1) +} + +func (m *MockMessage) ReadBody() (body *enmime.MIMEBody, err error) { + args := m.Called() + return args.Get(0).(*enmime.MIMEBody), args.Error(1) +} + +func (m *MockMessage) ReadRaw() (raw *string, err error) { + args := m.Called() + return args.Get(0).(*string), args.Error(1) +} + +func (m *MockMessage) RawReader() (reader io.ReadCloser, err error) { + args := m.Called() + return args.Get(0).(io.ReadCloser), args.Error(1) +} + +func (m *MockMessage) Size() int64 { + args := m.Called() + return int64(args.Int(0)) +} + +func (m *MockMessage) Append(data []byte) error { + // []byte arg seems to mess up testify/mock + return nil +} + +func (m *MockMessage) Close() error { + args := m.Called() + return args.Error(0) +} + +func (m *MockMessage) Delete() error { + args := m.Called() + return args.Error(0) +} + +func (m *MockMessage) String() string { + args := m.Called() + return args.String(0) +} diff --git a/web/server.go b/web/server.go index 1bac524..4c6de81 100644 --- a/web/server.go +++ b/web/server.go @@ -18,12 +18,25 @@ import ( type handler func(http.ResponseWriter, *http.Request, *Context) error +var webConfig config.WebConfig var DataStore smtpd.DataStore var Router *mux.Router var listener net.Listener var sessionStore sessions.Store var shutdown bool +// Initialize sets up things for unit tests or the Start() method +func Initialize(cfg config.WebConfig, ds smtpd.DataStore) { + webConfig = cfg + setupRoutes(cfg) + + // NewContext() will use this DataStore for the web handlers + DataStore = ds + + // TODO Make configurable + sessionStore = sessions.NewCookieStore([]byte("something-very-secret")) +} + func setupRoutes(cfg config.WebConfig) { log.LogInfo("Theme templates mapped to '%v'", cfg.TemplateDir) log.LogInfo("Theme static content mapped to '%v'", cfg.PublicDir) @@ -54,16 +67,7 @@ func setupRoutes(cfg config.WebConfig) { // Start() the web server func Start() { - cfg := config.GetWebConfig() - setupRoutes(cfg) - - // NewContext() will use this DataStore for the web handlers - DataStore = smtpd.DefaultFileDataStore() - - // TODO Make configurable - sessionStore = sessions.NewCookieStore([]byte("something-very-secret")) - - addr := fmt.Sprintf("%v:%v", cfg.Ip4address, cfg.Ip4port) + addr := fmt.Sprintf("%v:%v", webConfig.Ip4address, webConfig.Ip4port) server := &http.Server{ Addr: addr, Handler: nil, diff --git a/web/template.go b/web/template.go index fef8760..da6346f 100644 --- a/web/template.go +++ b/web/template.go @@ -1,7 +1,6 @@ package web import ( - "github.com/jhillyerd/inbucket/config" "github.com/jhillyerd/inbucket/log" "html/template" "net/http" @@ -49,9 +48,8 @@ func ParseTemplate(name string, partial bool) (*template.Template, error) { return t, nil } - cfg := config.GetWebConfig() tempPath := strings.Replace(name, "/", string(filepath.Separator), -1) - tempFile := filepath.Join(cfg.TemplateDir, tempPath) + tempFile := filepath.Join(webConfig.TemplateDir, tempPath) log.LogTrace("Parsing template %v", tempFile) var err error @@ -63,14 +61,14 @@ func ParseTemplate(name string, partial bool) (*template.Template, error) { t, err = t.ParseFiles(tempFile) } else { t = template.New("_base.html").Funcs(TemplateFuncs) - t, err = t.ParseFiles(filepath.Join(cfg.TemplateDir, "_base.html"), tempFile) + t, err = t.ParseFiles(filepath.Join(webConfig.TemplateDir, "_base.html"), tempFile) } if err != nil { return nil, err } // Allows us to disable caching for theme development - if cfg.TemplateCache { + if webConfig.TemplateCache { if partial { log.LogTrace("Caching partial %v", name) cachedTemplates[name] = t