From ddf949013c2ac141ddd3e21cff04855f72a254da Mon Sep 17 00:00:00 2001 From: Acho Arnold Date: Sat, 23 Jul 2022 13:51:39 +0300 Subject: [PATCH] Finish documentation --- README.md | 48 +++++++++++++++------------ client.go | 26 ++++----------- client_config.go | 7 ++-- client_option.go | 13 +++----- client_option_test.go | 27 ++++----------- go.mod | 2 +- http_status.go | 6 ---- internal/stubs/message.go | 39 ++++++++++++++++++++++ messages.go | 37 +++++++++++++++++++++ messages_service.go | 32 ++++++++++++++++++ messages_service_test.go | 69 ++++++++++++++++++++++++++++++++++++++ response.go | 2 +- status_service.go | 32 ------------------ status_service_test.go | 70 --------------------------------------- 14 files changed, 225 insertions(+), 185 deletions(-) delete mode 100644 http_status.go create mode 100644 internal/stubs/message.go create mode 100644 messages.go create mode 100644 messages_service.go create mode 100644 messages_service_test.go delete mode 100644 status_service.go delete mode 100644 status_service_test.go diff --git a/README.md b/README.md index 6b1bbae..a6b15e2 100644 --- a/README.md +++ b/README.md @@ -1,51 +1,51 @@ -# go-http-client +# httpsms-go -[![Build](https://github.com/NdoleStudio/go-http-client/actions/workflows/main.yml/badge.svg)](https://github.com/NdoleStudio/go-http-client/actions/workflows/main.yml) -[![codecov](https://codecov.io/gh/NdoleStudio/go-http-client/branch/main/graph/badge.svg)](https://codecov.io/gh/NdoleStudio/go-http-client) -[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/NdoleStudio/go-http-client/badges/quality-score.png?b=main)](https://scrutinizer-ci.com/g/NdoleStudio/go-http-client/?branch=main) -[![Go Report Card](https://goreportcard.com/badge/github.com/NdoleStudio/go-http-client)](https://goreportcard.com/report/github.com/NdoleStudio/go-http-client) -[![GitHub contributors](https://img.shields.io/github/contributors/NdoleStudio/go-http-client)](https://github.com/NdoleStudio/go-http-client/graphs/contributors) -[![GitHub license](https://img.shields.io/github/license/NdoleStudio/go-http-client?color=brightgreen)](https://github.com/NdoleStudio/go-http-client/blob/master/LICENSE) -[![PkgGoDev](https://pkg.go.dev/badge/github.com/NdoleStudio/go-http-client)](https://pkg.go.dev/github.com/NdoleStudio/go-http-client) +[![Build](https://github.com/NdoleStudio/httpsms-go/actions/workflows/main.yml/badge.svg)](https://github.com/NdoleStudio/httpsms-go/actions/workflows/main.yml) +[![codecov](https://codecov.io/gh/NdoleStudio/httpsms-go/branch/main/graph/badge.svg)](https://codecov.io/gh/NdoleStudio/httpsms-go) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/NdoleStudio/httpsms-go/badges/quality-score.png?b=main)](https://scrutinizer-ci.com/g/NdoleStudio/httpsms-go/?branch=main) +[![Go Report Card](https://goreportcard.com/badge/github.com/NdoleStudio/httpsms-go)](https://goreportcard.com/report/github.com/NdoleStudio/httpsms-go) +[![GitHub contributors](https://img.shields.io/github/contributors/NdoleStudio/httpsms-go)](https://github.com/NdoleStudio/httpsms-go/graphs/contributors) +[![GitHub license](https://img.shields.io/github/license/NdoleStudio/httpsms-go?color=brightgreen)](https://github.com/NdoleStudio/httpsms-go/blob/master/LICENSE) +[![PkgGoDev](https://pkg.go.dev/badge/github.com/NdoleStudio/httpsms-go)](https://pkg.go.dev/github.com/NdoleStudio/httpsms-go) -This package provides a generic `go` client template for an HTTP API +This package provides a generic `go` client template for the Http SMS Api ## Installation -`go-http-client` is compatible with modern Go releases in module mode, with Go installed: +`httpsms-go` is compatible with modern Go releases in module mode, with Go installed: ```bash -go get github.com/NdoleStudio/go-http-client +go get github.com/NdoleStudio/httpsms-go ``` Alternatively the same can be achieved if you use `import` in a package: ```go -import "github.com/NdoleStudio/go-http-client" +import "github.com/NdoleStudio/httpsms-go" ``` ## Implemented -- [Status Codes](#status-codes) - - `GET /200`: OK +- [Messages](#messages) + - `POST /v1/messages/send`: Send a new SMS Message ## Usage ### Initializing the Client -An instance of the client can be created using `New()`. +An instance of the client can be created using `httpsms.New()`. ```go package main import ( - "github.com/NdoleStudio/go-http-client" + "github.com/NdoleStudio/httpsms-go" ) func main() { - statusClient := client.New(client.WithDelay(200)) + client := htpsms.New(htpsms.WithDelay(200)) } ``` @@ -54,24 +54,28 @@ func main() { All API calls return an `error` as the last return object. All successful calls will return a `nil` error. ```go -status, response, err := statusClient.Status.Ok(context.Background()) +_, response, err := client.Messages.Send(context.Background()) if err != nil { //handle error } ``` -### Status Codes +### Messages -#### `GET /200`: OK +#### `POST /v1/messages/send`: Send a new SMS Message ```go -status, response, err := statusClient.Status.Ok(context.Background()) +message, response, err := client.Messages.Send(context.Background(), &MessageSendParams{ + Content: "This is a sample text message", + From: "+18005550199", + To: "+18005550100", +}) if err != nil { log.Fatal(err) } -log.Println(status.Description) // OK +log.Println(message.Code) // 202 ``` ## Testing diff --git a/client.go b/client.go index 670a6d5..88bf7ee 100644 --- a/client.go +++ b/client.go @@ -1,4 +1,4 @@ -package client +package httpsms import ( "bytes" @@ -8,7 +8,6 @@ import ( "io" "io/ioutil" "net/http" - "strconv" ) type service struct { @@ -21,9 +20,9 @@ type Client struct { httpClient *http.Client common service baseURL string - delay int + apiKey string - Status *statusService + Messages *messagesService } // New creates and returns a new campay.Client from a slice of campay.ClientOption. @@ -36,12 +35,12 @@ func New(options ...Option) *Client { client := &Client{ httpClient: config.httpClient, - delay: config.delay, + apiKey: config.apiKey, baseURL: config.baseURL, } client.common.client = client - client.Status = (*statusService)(&client.common) + client.Messages = (*messagesService)(&client.common) return client } @@ -67,24 +66,11 @@ func (client *Client) newRequest(ctx context.Context, method, uri string, body i req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json") - - if client.delay > 0 { - client.addURLParams(req, map[string]string{"sleep": strconv.Itoa(client.delay)}) - } + req.Header.Set("x-api-key", client.apiKey) return req, nil } -// addURLParams adds urls parameters to an *http.Request -func (client *Client) addURLParams(request *http.Request, params map[string]string) *http.Request { - q := request.URL.Query() - for key, value := range params { - q.Add(key, value) - } - request.URL.RawQuery = q.Encode() - return request -} - // do carries out an HTTP request and returns a Response func (client *Client) do(req *http.Request) (*Response, error) { if req == nil { diff --git a/client_config.go b/client_config.go index c9c68c8..52ec27d 100644 --- a/client_config.go +++ b/client_config.go @@ -1,17 +1,16 @@ -package client +package httpsms import "net/http" type clientConfig struct { httpClient *http.Client - delay int + apiKey string baseURL string } func defaultClientConfig() *clientConfig { return &clientConfig{ httpClient: http.DefaultClient, - delay: 0, - baseURL: "https://httpstat.us", + baseURL: "https://api.httpsms.com", } } diff --git a/client_option.go b/client_option.go index 3001106..c3e354c 100644 --- a/client_option.go +++ b/client_option.go @@ -1,4 +1,4 @@ -package client +package httpsms import ( "net/http" @@ -26,7 +26,7 @@ func WithHTTPClient(httpClient *http.Client) Option { }) } -// WithBaseURL set's the base url for the flutterwave API +// WithBaseURL sets the base url for the httpsms API func WithBaseURL(baseURL string) Option { return clientOptionFunc(func(config *clientConfig) { if baseURL != "" { @@ -35,12 +35,9 @@ func WithBaseURL(baseURL string) Option { }) } -// WithDelay sets the delay in milliseconds before a response is gotten. -// The delay must be > 0 for it to be used. -func WithDelay(delay int) Option { +// WithAPIKey sets the api key for the httpsms API +func WithAPIKey(apiKey string) Option { return clientOptionFunc(func(config *clientConfig) { - if delay > 0 { - config.delay = delay - } + config.apiKey = apiKey }) } diff --git a/client_option_test.go b/client_option_test.go index 7756017..95993b3 100644 --- a/client_option_test.go +++ b/client_option_test.go @@ -1,4 +1,4 @@ -package client +package httpsms import ( "net/http" @@ -71,34 +71,19 @@ func TestWithBaseURL(t *testing.T) { }) } -func TestWithDelay(t *testing.T) { - t.Run("delay is set successfully", func(t *testing.T) { +func TestWithAPIKey(t *testing.T) { + t.Run("api key is set successfully", func(t *testing.T) { // Setup t.Parallel() // Arrange config := defaultClientConfig() - delay := 1 + apiKey := "x-api-key" // Act - WithDelay(delay).apply(config) + WithAPIKey(apiKey).apply(config) // Assert - assert.Equal(t, delay, config.delay) - }) - - t.Run("delay is not set when value < 0", func(t *testing.T) { - // Setup - t.Parallel() - - // Arrange - config := defaultClientConfig() - delay := -1 - - // Act - WithDelay(delay).apply(config) - - // Assert - assert.Equal(t, 0, config.delay) + assert.Equal(t, apiKey, config.apiKey) }) } diff --git a/go.mod b/go.mod index 5e43425..9fa9756 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/NdoleStudio/go-http-client +module github.com/NdoleStudio/httpsms-go go 1.17 diff --git a/http_status.go b/http_status.go deleted file mode 100644 index 798523f..0000000 --- a/http_status.go +++ /dev/null @@ -1,6 +0,0 @@ -package client - -type HTTPStatus struct { - Code int `json:"code"` - Description string `json:"description"` -} diff --git a/internal/stubs/message.go b/internal/stubs/message.go new file mode 100644 index 0000000..8c5a7c8 --- /dev/null +++ b/internal/stubs/message.go @@ -0,0 +1,39 @@ +package stubs + +// MessagesSendResponse response from the /v1/messages/send endpoint +func MessagesSendResponse() []byte { + return []byte(` + { + "data": { + "contact": "+18005550100", + "content": "This is a sample text message", + "created_at": "2022-06-05T14:26:02.302718+03:00", + "failure_reason": "", + "id": "32343a19-da5e-4b1b-a767-3298a73703cb", + "last_attempted_at": "2022-06-05T14:26:09.527976+03:00", + "order_timestamp": "2022-06-05T14:26:09.527976+03:00", + "owner": "+18005550199", + "received_at": "2022-06-05T14:26:09.527976+03:00", + "request_received_at": "2022-06-05T14:26:01.520828+03:00", + "send_time": 133414, + "sent_at": "2022-06-05T14:26:09.527976+03:00", + "status": "pending", + "type": "mobile-terminated", + "updated_at": "2022-06-05T14:26:10.303278+03:00", + "user_id": "WB7DRDWrJZRGbYrv2CKGkqbzvqdC" + }, + "message": "message created successfully", + "status": "success" + } +`) +} + +// MessagesSendErrorResponse internal error response +func MessagesSendErrorResponse() []byte { + return []byte(` + { + "message": "We ran into an internal error while handling the request.", + "status": "error" + } +`) +} diff --git a/messages.go b/messages.go new file mode 100644 index 0000000..c25b4c1 --- /dev/null +++ b/messages.go @@ -0,0 +1,37 @@ +package httpsms + +import "time" + +// MessageSendParams is the request payload for sending a message +type MessageSendParams struct { + Content string `json:"content"` + From string `json:"from"` + To string `json:"to"` +} + +// MessageResponse is the response gotten with a message content +type MessageResponse struct { + Data Message `json:"data"` + Message string `json:"message"` + Status string `json:"status"` +} + +// Message represents and incoming or outgoing SMS message +type Message struct { + Contact string `json:"contact"` + Content string `json:"content"` + CreatedAt time.Time `json:"created_at"` + FailureReason string `json:"failure_reason"` + ID string `json:"id"` + LastAttemptedAt time.Time `json:"last_attempted_at"` + OrderTimestamp time.Time `json:"order_timestamp"` + Owner string `json:"owner"` + ReceivedAt time.Time `json:"received_at"` + RequestReceivedAt time.Time `json:"request_received_at"` + SendTime int `json:"send_time"` + SentAt time.Time `json:"sent_at"` + Status string `json:"status"` + Type string `json:"type"` + UpdatedAt time.Time `json:"updated_at"` + UserID string `json:"user_id"` +} diff --git a/messages_service.go b/messages_service.go new file mode 100644 index 0000000..32c8918 --- /dev/null +++ b/messages_service.go @@ -0,0 +1,32 @@ +package httpsms + +import ( + "context" + "encoding/json" + "net/http" +) + +// messagesService is the API client for the `/` endpoint +type messagesService service + +// Send adds a new SMS message to be sent by the android phone +// +// API Docs: https://api.httpsms.com/index.html#/Messages/post_messages_send +func (service *messagesService) Send(ctx context.Context, params *MessageSendParams) (*MessageResponse, *Response, error) { + request, err := service.client.newRequest(ctx, http.MethodPost, "/v1/messages/send", params) + if err != nil { + return nil, nil, err + } + + response, err := service.client.do(request) + if err != nil { + return nil, response, err + } + + message := new(MessageResponse) + if err = json.Unmarshal(*response.Body, message); err != nil { + return nil, response, err + } + + return message, response, nil +} diff --git a/messages_service_test.go b/messages_service_test.go new file mode 100644 index 0000000..a4d339e --- /dev/null +++ b/messages_service_test.go @@ -0,0 +1,69 @@ +package httpsms + +import ( + "context" + "encoding/json" + "net/http" + "testing" + + "github.com/NdoleStudio/httpsms-go/internal/helpers" + "github.com/NdoleStudio/httpsms-go/internal/stubs" + "github.com/stretchr/testify/assert" +) + +const ( + fromNumber = "+18005550199" + toNumber = "+18005550100" + messageContent = "This is a sample text message" +) + +func TestMessagesService_Send(t *testing.T) { + // Setup + t.Parallel() + + // Arrange + apiKey := "test-api-key" + server := helpers.MakeTestServer(http.StatusOK, stubs.MessagesSendResponse()) + client := New(WithBaseURL(server.URL), WithAPIKey(apiKey)) + + sendParams := &MessageSendParams{ + Content: messageContent, + From: fromNumber, + To: toNumber, + } + + // Act + message, response, err := client.Messages.Send(context.Background(), sendParams) + + // Assert + assert.Nil(t, err) + + assert.Equal(t, http.StatusOK, response.HTTPResponse.StatusCode) + + jsonContent, _ := json.Marshal(message) + assert.JSONEq(t, string(stubs.MessagesSendResponse()), string(jsonContent)) + + // Teardown + server.Close() +} + +func TestMessagesService_SendWithError(t *testing.T) { + // Setup + t.Parallel() + + // Arrange + apiKey := "test-api-key" + server := helpers.MakeTestServer(http.StatusInternalServerError, stubs.MessagesSendErrorResponse()) + client := New(WithBaseURL(server.URL), WithAPIKey(apiKey)) + + // Act + _, response, err := client.Messages.Send(context.Background(), &MessageSendParams{}) + + // Assert + assert.NotNil(t, err) + assert.Equal(t, http.StatusInternalServerError, response.HTTPResponse.StatusCode) + assert.Equal(t, string(stubs.MessagesSendErrorResponse()), string(*response.Body)) + + // Teardown + server.Close() +} diff --git a/response.go b/response.go index eb1adce..1b2cb85 100644 --- a/response.go +++ b/response.go @@ -1,4 +1,4 @@ -package client +package httpsms import ( "bytes" diff --git a/status_service.go b/status_service.go deleted file mode 100644 index 385cac4..0000000 --- a/status_service.go +++ /dev/null @@ -1,32 +0,0 @@ -package client - -import ( - "context" - "encoding/json" - "net/http" -) - -// statusService is the API client for the `/` endpoint -type statusService service - -// Ok returns the 200 HTTP status Code. -// -// API Docs: https://httpstat.us -func (service *statusService) Ok(ctx context.Context) (*HTTPStatus, *Response, error) { - request, err := service.client.newRequest(ctx, http.MethodGet, "/200", nil) - if err != nil { - return nil, nil, err - } - - response, err := service.client.do(request) - if err != nil { - return nil, response, err - } - - status := new(HTTPStatus) - if err = json.Unmarshal(*response.Body, status); err != nil { - return nil, response, err - } - - return status, response, nil -} diff --git a/status_service_test.go b/status_service_test.go deleted file mode 100644 index 85e57fd..0000000 --- a/status_service_test.go +++ /dev/null @@ -1,70 +0,0 @@ -package client - -import ( - "context" - "net/http" - "testing" - "time" - - "github.com/NdoleStudio/go-http-client/internal/helpers" - "github.com/stretchr/testify/assert" -) - -func TestStatusService_Ok(t *testing.T) { - // Setup - t.Parallel() - - // Arrange - client := New() - - // Act - status, response, err := client.Status.Ok(context.Background()) - - // Assert - assert.Nil(t, err) - - assert.Equal(t, http.StatusOK, response.HTTPResponse.StatusCode) - assert.Equal(t, &HTTPStatus{Code: 200, Description: "OK"}, status) -} - -func TestBillsService_OkWithDelay(t *testing.T) { - // Setup - t.Parallel() - start := time.Now() - - // Arrange - client := New(WithDelay(500)) - - // Act - status, response, err := client.Status.Ok(context.Background()) - - // Assert - assert.Nil(t, err) - assert.LessOrEqual(t, int64(100), time.Since(start).Milliseconds()) - assert.Equal(t, http.StatusOK, response.HTTPResponse.StatusCode) - assert.Equal(t, &HTTPStatus{Code: 200, Description: "OK"}, status) -} - -func TestBillsService_OkWithError(t *testing.T) { - // Setup - t.Parallel() - - // Arrange - server := helpers.MakeTestServer(http.StatusInternalServerError, []byte("Internal Server Error")) - client := New(WithBaseURL(server.URL)) - - // Act - status, response, err := client.Status.Ok(context.Background()) - - // Assert - assert.NotNil(t, err) - assert.Nil(t, status) - - assert.Equal(t, "500: Internal Server Error, Body: Internal Server Error", err.Error()) - - assert.Equal(t, http.StatusInternalServerError, response.HTTPResponse.StatusCode) - assert.Equal(t, "Internal Server Error", string(*response.Body)) - - // Teardown - server.Close() -}