From a1e35009e0c2e527b4c121b80d4a88fc2792198f Mon Sep 17 00:00:00 2001 From: James Hillyerd Date: Sat, 4 Feb 2017 16:14:40 -0800 Subject: [PATCH] Add convenience methods to rest/client types --- rest/client/apiv1_client.go | 68 +++++++++-- rest/client/apiv1_client_test.go | 200 +++++++++++++++++++++++++++++-- rest/client/rest.go | 5 +- rest/client/rest_test.go | 13 +- 4 files changed, 258 insertions(+), 28 deletions(-) diff --git a/rest/client/apiv1_client.go b/rest/client/apiv1_client.go index 5ad5245..d75bfa3 100644 --- a/rest/client/apiv1_client.go +++ b/rest/client/apiv1_client.go @@ -1,3 +1,4 @@ +// Package client provides a basic REST client for Inbucket package client import ( @@ -10,19 +11,19 @@ import ( "github.com/jhillyerd/inbucket/rest/model" ) -// ClientV1 accesses the Inbucket REST API v1 -type ClientV1 struct { +// Client accesses the Inbucket REST API v1 +type Client struct { restClient } -// NewV1 creates a new v1 REST API client given the base URL of an Inbucket server, ex: +// New creates a new v1 REST API client given the base URL of an Inbucket server, ex: // "http://localhost:9000" -func NewV1(baseURL string) (*ClientV1, error) { +func New(baseURL string) (*Client, error) { parsedURL, err := url.Parse(baseURL) if err != nil { return nil, err } - c := &ClientV1{ + c := &Client{ restClient{ client: &http.Client{ Timeout: 30 * time.Second, @@ -34,21 +35,31 @@ func NewV1(baseURL string) (*ClientV1, error) { } // ListMailbox returns a list of messages for the requested mailbox -func (c *ClientV1) ListMailbox(name string) (headers []*model.JSONMessageHeaderV1, err error) { +func (c *Client) ListMailbox(name string) (headers []*MessageHeader, err error) { uri := "/api/v1/mailbox/" + url.QueryEscape(name) err = c.doJSON("GET", uri, &headers) + if err != nil { + return nil, err + } + for _, h := range headers { + h.client = c + } return } // GetMessage returns the message details given a mailbox name and message ID. -func (c *ClientV1) GetMessage(name, id string) (message *model.JSONMessageV1, err error) { +func (c *Client) GetMessage(name, id string) (message *Message, err error) { uri := "/api/v1/mailbox/" + url.QueryEscape(name) + "/" + id err = c.doJSON("GET", uri, &message) + if err != nil { + return nil, err + } + message.client = c return } // GetMessageSource returns the message source given a mailbox name and message ID. -func (c *ClientV1) GetMessageSource(name, id string) (*bytes.Buffer, error) { +func (c *Client) GetMessageSource(name, id string) (*bytes.Buffer, error) { uri := "/api/v1/mailbox/" + url.QueryEscape(name) + "/" + id + "/source" resp, err := c.do("GET", uri) if err != nil { @@ -68,7 +79,7 @@ func (c *ClientV1) GetMessageSource(name, id string) (*bytes.Buffer, error) { } // DeleteMessage deletes a single message given the mailbox name and message ID. -func (c *ClientV1) DeleteMessage(name, id string) error { +func (c *Client) DeleteMessage(name, id string) error { uri := "/api/v1/mailbox/" + url.QueryEscape(name) + "/" + id resp, err := c.do("DELETE", uri) if err != nil { @@ -82,7 +93,7 @@ func (c *ClientV1) DeleteMessage(name, id string) error { } // PurgeMailbox deletes all messages in the given mailbox -func (c *ClientV1) PurgeMailbox(name string) error { +func (c *Client) PurgeMailbox(name string) error { uri := "/api/v1/mailbox/" + url.QueryEscape(name) resp, err := c.do("DELETE", uri) if err != nil { @@ -94,3 +105,40 @@ func (c *ClientV1) PurgeMailbox(name string) error { } return nil } + +// MessageHeader represents an Inbucket message sans content +type MessageHeader struct { + *model.JSONMessageHeaderV1 + client *Client +} + +// GetMessage returns this message with content +func (h *MessageHeader) GetMessage() (message *Message, err error) { + return h.client.GetMessage(h.Mailbox, h.ID) +} + +// GetSource returns the source for this message +func (h *MessageHeader) GetSource() (*bytes.Buffer, error) { + return h.client.GetMessageSource(h.Mailbox, h.ID) +} + +// Delete deletes this message from the mailbox +func (h *MessageHeader) Delete() error { + return h.client.DeleteMessage(h.Mailbox, h.ID) +} + +// Message represents an Inbucket message including content +type Message struct { + *model.JSONMessageV1 + client *Client +} + +// GetSource returns the source for this message +func (m *Message) GetSource() (*bytes.Buffer, error) { + return m.client.GetMessageSource(m.Mailbox, m.ID) +} + +// Delete deletes this message from the mailbox +func (m *Message) Delete() error { + return m.client.DeleteMessage(m.Mailbox, m.ID) +} diff --git a/rest/client/apiv1_client_test.go b/rest/client/apiv1_client_test.go index 1e8ddc7..abce5ca 100644 --- a/rest/client/apiv1_client_test.go +++ b/rest/client/apiv1_client_test.go @@ -5,7 +5,7 @@ import "testing" func TestClientV1ListMailbox(t *testing.T) { var want, got string - c, err := NewV1(baseURLStr) + c, err := New(baseURLStr) if err != nil { t.Fatal(err) } @@ -13,7 +13,7 @@ func TestClientV1ListMailbox(t *testing.T) { c.client = mth // Method under test - c.ListMailbox("testbox") + _, _ = c.ListMailbox("testbox") want = "GET" got = mth.req.Method @@ -31,7 +31,7 @@ func TestClientV1ListMailbox(t *testing.T) { func TestClientV1GetMessage(t *testing.T) { var want, got string - c, err := NewV1(baseURLStr) + c, err := New(baseURLStr) if err != nil { t.Fatal(err) } @@ -39,7 +39,7 @@ func TestClientV1GetMessage(t *testing.T) { c.client = mth // Method under test - c.GetMessage("testbox", "20170107T224128-0000") + _, _ = c.GetMessage("testbox", "20170107T224128-0000") want = "GET" got = mth.req.Method @@ -57,13 +57,12 @@ func TestClientV1GetMessage(t *testing.T) { func TestClientV1GetMessageSource(t *testing.T) { var want, got string - c, err := NewV1(baseURLStr) + c, err := New(baseURLStr) if err != nil { t.Fatal(err) } mth := &mockHTTPClient{ - statusCode: 200, - body: "message source", + body: "message source", } c.client = mth @@ -95,7 +94,7 @@ func TestClientV1GetMessageSource(t *testing.T) { func TestClientV1DeleteMessage(t *testing.T) { var want, got string - c, err := NewV1(baseURLStr) + c, err := New(baseURLStr) if err != nil { t.Fatal(err) } @@ -103,7 +102,10 @@ func TestClientV1DeleteMessage(t *testing.T) { c.client = mth // Method under test - c.DeleteMessage("testbox", "20170107T224128-0000") + err = c.DeleteMessage("testbox", "20170107T224128-0000") + if err != nil { + t.Fatal(err) + } want = "DELETE" got = mth.req.Method @@ -121,7 +123,7 @@ func TestClientV1DeleteMessage(t *testing.T) { func TestClientV1PurgeMailbox(t *testing.T) { var want, got string - c, err := NewV1(baseURLStr) + c, err := New(baseURLStr) if err != nil { t.Fatal(err) } @@ -129,7 +131,10 @@ func TestClientV1PurgeMailbox(t *testing.T) { c.client = mth // Method under test - c.PurgeMailbox("testbox") + err = c.PurgeMailbox("testbox") + if err != nil { + t.Fatal(err) + } want = "DELETE" got = mth.req.Method @@ -143,3 +148,176 @@ func TestClientV1PurgeMailbox(t *testing.T) { t.Errorf("req.URL == %q, want %q", got, want) } } + +func TestClientV1MessageHeader(t *testing.T) { + var want, got string + response := `[ + { + "mailbox":"mailbox1", + "id":"id1", + "from":"from1", + "subject":"subject1", + "date":"2017-01-01T00:00:00.000-07:00", + "size":100 + } + ]` + + c, err := New(baseURLStr) + if err != nil { + t.Fatal(err) + } + mth := &mockHTTPClient{body: response} + c.client = mth + + // Method under test + headers, err := c.ListMailbox("testbox") + if err != nil { + t.Fatal(err) + } + + want = "GET" + got = mth.req.Method + if got != want { + t.Errorf("req.Method == %q, want %q", got, want) + } + + want = baseURLStr + "/api/v1/mailbox/testbox" + got = mth.req.URL.String() + if got != want { + t.Errorf("req.URL == %q, want %q", got, want) + } + + if len(headers) != 1 { + t.Fatalf("len(headers) == %v, want 1", len(headers)) + } + header := headers[0] + + want = "mailbox1" + got = header.Mailbox + if got != want { + t.Errorf("Mailbox == %q, want %q", got, want) + } + + want = "id1" + got = header.ID + if got != want { + t.Errorf("ID == %q, want %q", got, want) + } + + want = "from1" + got = header.From + if got != want { + t.Errorf("From == %q, want %q", got, want) + } + + want = "subject1" + got = header.Subject + if got != want { + t.Errorf("Subject == %q, want %q", got, want) + } + + // Test MessageHeader.Delete() + mth.body = "" + err = header.Delete() + if err != nil { + t.Fatal(err) + } + + want = "DELETE" + got = mth.req.Method + if got != want { + t.Errorf("req.Method == %q, want %q", got, want) + } + + want = baseURLStr + "/api/v1/mailbox/mailbox1/id1" + got = mth.req.URL.String() + if got != want { + t.Errorf("req.URL == %q, want %q", got, want) + } + + // Test MessageHeader.GetSource() + mth.body = "source1" + _, err = header.GetSource() + if err != nil { + t.Fatal(err) + } + + want = "GET" + got = mth.req.Method + if got != want { + t.Errorf("req.Method == %q, want %q", got, want) + } + + want = baseURLStr + "/api/v1/mailbox/mailbox1/id1/source" + got = mth.req.URL.String() + if got != want { + t.Errorf("req.URL == %q, want %q", got, want) + } + + // Test MessageHeader.GetMessage() + mth.body = `{ + "mailbox":"mailbox1", + "id":"id1", + "from":"from1", + "subject":"subject1", + "date":"2017-01-01T00:00:00.000-07:00", + "size":100 + }` + message, err := header.GetMessage() + if err != nil { + t.Fatal(err) + } + if message == nil { + t.Fatalf("message was nil, wanted a value") + } + + want = "GET" + got = mth.req.Method + if got != want { + t.Errorf("req.Method == %q, want %q", got, want) + } + + want = baseURLStr + "/api/v1/mailbox/mailbox1/id1" + got = mth.req.URL.String() + if got != want { + t.Errorf("req.URL == %q, want %q", got, want) + } + + // Test Message.Delete() + mth.body = "" + err = message.Delete() + if err != nil { + t.Fatal(err) + } + + want = "DELETE" + got = mth.req.Method + if got != want { + t.Errorf("req.Method == %q, want %q", got, want) + } + + want = baseURLStr + "/api/v1/mailbox/mailbox1/id1" + got = mth.req.URL.String() + if got != want { + t.Errorf("req.URL == %q, want %q", got, want) + } + + // Test MessageHeader.GetSource() + mth.body = "source1" + _, err = message.GetSource() + if err != nil { + t.Fatal(err) + } + + want = "GET" + got = mth.req.Method + if got != want { + t.Errorf("req.Method == %q, want %q", got, want) + } + + want = baseURLStr + "/api/v1/mailbox/mailbox1/id1/source" + got = mth.req.URL.String() + if got != want { + t.Errorf("req.URL == %q, want %q", got, want) + } +} diff --git a/rest/client/rest.go b/rest/client/rest.go index 1878052..718749d 100644 --- a/rest/client/rest.go +++ b/rest/client/rest.go @@ -50,10 +50,9 @@ func (c *restClient) doJSON(method string, uri string, v interface{}) error { if resp.StatusCode == http.StatusOK { if v == nil { return nil - } else { - // Decode response body - return json.NewDecoder(resp.Body).Decode(v) } + // Decode response body + return json.NewDecoder(resp.Body).Decode(v) } return fmt.Errorf("Unexpected HTTP response status %v: %s", resp.StatusCode, resp.Status) diff --git a/rest/client/rest_test.go b/rest/client/rest_test.go index 05eb6a7..3668578 100644 --- a/rest/client/rest_test.go +++ b/rest/client/rest_test.go @@ -28,6 +28,9 @@ type mockHTTPClient struct { func (m *mockHTTPClient) Do(req *http.Request) (resp *http.Response, err error) { m.req = req + if m.statusCode == 0 { + m.statusCode = 200 + } resp = &http.Response{ StatusCode: m.statusCode, Body: ioutil.NopCloser(bytes.NewBufferString(m.body)), @@ -64,13 +67,15 @@ func TestDoJSON(t *testing.T) { var want, got string mth := &mockHTTPClient{ - statusCode: 200, - body: `{"foo": "bar"}`, + body: `{"foo": "bar"}`, } c := &restClient{mth, baseURL} var v map[string]interface{} - c.doJSON("GET", "/doget", &v) + err := c.doJSON("GET", "/doget", &v) + if err != nil { + t.Fatal(err) + } want = "GET" got = mth.req.Method @@ -98,7 +103,7 @@ func TestDoJSON(t *testing.T) { func TestDoJSONNilV(t *testing.T) { var want, got string - mth := &mockHTTPClient{statusCode: 200} + mth := &mockHTTPClient{} c := &restClient{mth, baseURL} err := c.doJSON("GET", "/doget", nil)