Skip to content

Commit

Permalink
chore: change sdk initialization
Browse files Browse the repository at this point in the history
- use `internal` unexported SIL client
  • Loading branch information
Muchogoc committed Oct 11, 2022
1 parent 927d761 commit a7a89b5
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 89 deletions.
53 changes: 30 additions & 23 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ import (
)

var (
// commsBaseURL represents the SIL-Comms base URL
commsBaseURL = serverutils.MustGetEnvVar("SIL_COMMS_BASE_URL")
// BaseURL represents the SIL-Comms base URL
BaseURL = serverutils.MustGetEnvVar("SIL_COMMS_BASE_URL")

// commsEmail is used for authentication against the SIL comms API
commsEmail = serverutils.MustGetEnvVar("SIL_COMMS_EMAIL")
// email is used for authentication against the SIL comms API
email = serverutils.MustGetEnvVar("SIL_COMMS_EMAIL")

// commsPassword is used for authentication against the SIL comms API
commsPassword = serverutils.MustGetEnvVar("SIL_COMMS_PASSWORD")
// password is used for authentication against the SIL comms API
password = serverutils.MustGetEnvVar("SIL_COMMS_PASSWORD")

// accessTokenTimeout shows the access token expiry time.
// After the access token expires, one is required to obtain a new one
Expand All @@ -32,9 +32,9 @@ var (
refreshTokenTimeout = 24 * time.Hour
)

// CommsClient is the client used to make API request to sil communications API
type CommsClient struct {
client http.Client
// It is the client used to make API request to sil communications API
type client struct {
client *http.Client

refreshToken string
refreshTokenTicker *time.Ticker
Expand All @@ -43,21 +43,26 @@ type CommsClient struct {
accessTokenTicker *time.Ticker
}

// NewSILCommsClient initializes a new SIL comms client instance
func NewSILCommsClient() *CommsClient {
s := &CommsClient{
client: http.Client{},
// newClient initializes a new SIL comms client instance
func newClient() *client {
s := &client{
client: &http.Client{
Timeout: time.Second * 10,
},
accessToken: "",
refreshToken: "",
}

s.login()

// set up background routine to update tokens
go s.background()

return s
}

// executed as a go routine to update the api tokens when they timeout
func (s *CommsClient) background() {
func (s *client) background() {
for {
select {
case t := <-s.refreshTokenTicker.C:
Expand All @@ -72,7 +77,8 @@ func (s *CommsClient) background() {
}
}

func (s *CommsClient) setAccessToken(token string) {
// setAccessToken sets the access token and updates the ticker timer
func (s *client) setAccessToken(token string) {
s.accessToken = token
if s.accessTokenTicker != nil {
s.accessTokenTicker.Reset(accessTokenTimeout)
Expand All @@ -81,7 +87,8 @@ func (s *CommsClient) setAccessToken(token string) {
}
}

func (s *CommsClient) setRefreshToken(token string) {
// setRefreshToken sets the access token and updates the ticker timer
func (s *client) setRefreshToken(token string) {
s.refreshToken = token
if s.refreshTokenTicker != nil {
s.refreshTokenTicker.Reset(refreshTokenTimeout)
Expand All @@ -92,14 +99,14 @@ func (s *CommsClient) setRefreshToken(token string) {

// login uses the provided credentials to login to the SIL communications backend
// It obtains the necessary tokens required to make authenticated requests
func (s *CommsClient) login() {
func (s *client) login() {
path := "/auth/token/"
payload := struct {
Email string `json:"email"`
Password string `json:"password"`
}{
Email: commsEmail,
Password: commsPassword,
Email: email,
Password: password,
}

response, err := s.MakeRequest(context.Background(), http.MethodPost, path, nil, payload, false)
Expand Down Expand Up @@ -132,7 +139,7 @@ func (s *CommsClient) login() {

}

func (s *CommsClient) refreshAccessToken() {
func (s *client) refreshAccessToken() {
path := "/auth/token/refresh/"
payload := struct {
Refresh string `json:"refresh"`
Expand Down Expand Up @@ -169,9 +176,9 @@ func (s *CommsClient) refreshAccessToken() {

}

// MakeRequest performs a HTTP request to the provided path
func (s *CommsClient) MakeRequest(ctx context.Context, method, path string, queryParams map[string]string, body interface{}, authorised bool) (*http.Response, error) {
urlPath := fmt.Sprintf("%s%s", commsBaseURL, path)
// MakeRequest performs a HTTP request to the provided path and parameters
func (s *client) MakeRequest(ctx context.Context, method, path string, queryParams map[string]string, body interface{}, authorised bool) (*http.Response, error) {
urlPath := fmt.Sprintf("%s%s", BaseURL, path)
var request *http.Request

switch method {
Expand Down
57 changes: 33 additions & 24 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,31 @@ package silcomms

import (
"context"
"fmt"
"net/http"
"testing"

"github.com/jarcoal/httpmock"
)

// MockLogin mocks a mock login request to obtain a token
func MockLogin() {
httpmock.RegisterResponder(http.MethodPost, fmt.Sprintf("%s/auth/token/", BaseURL), func(r *http.Request) (*http.Response, error) {
resp := APIResponse{
Status: StatusSuccess,
Message: "success",
Data: TokenResponse{
Refresh: "refresh",
Access: "access",
},
}
return httpmock.NewJsonResponse(http.StatusOK, resp)
})
}

func TestSILComms_Login(t *testing.T) {
type fields struct {
client http.Client
client *http.Client
}
tests := []struct {
name string
Expand All @@ -19,7 +35,7 @@ func TestSILComms_Login(t *testing.T) {
{
name: "happy case: successful login",
fields: fields{
client: http.Client{},
client: &http.Client{},
},
},
}
Expand All @@ -42,26 +58,28 @@ func TestSILComms_Login(t *testing.T) {
})
}

s := &CommsClient{
s := &client{
client: tt.fields.client,
}
s.login()
})
}
}

func TestSILCommsClient_refreshAccessToken(t *testing.T) {
func TestSILclient_refreshAccessToken(t *testing.T) {
type fields struct {
client http.Client
client *http.Client
refreshToken string
}
tests := []struct {
name string
fields fields
}{
{
name: "happy case: refresh access token",
fields: fields{},
name: "happy case: refresh access token",
fields: fields{
client: &http.Client{},
},
},
}
for _, tt := range tests {
Expand All @@ -83,7 +101,7 @@ func TestSILCommsClient_refreshAccessToken(t *testing.T) {
})
}

s := &CommsClient{
s := &client{
client: tt.fields.client,
refreshToken: tt.fields.refreshToken,
}
Expand All @@ -92,11 +110,7 @@ func TestSILCommsClient_refreshAccessToken(t *testing.T) {
}
}

func TestSILCommsClient_MakeRequest(t *testing.T) {
type fields struct {
client http.Client
accessToken string
}
func TestSILclient_MakeRequest(t *testing.T) {
type args struct {
ctx context.Context
method string
Expand All @@ -107,13 +121,11 @@ func TestSILCommsClient_MakeRequest(t *testing.T) {
}
tests := []struct {
name string
fields fields
args args
wantErr bool
}{
{
name: "happy case: make authenticated request",
fields: fields{},
name: "happy case: make authenticated request",
args: args{
ctx: context.Background(),
method: http.MethodPost,
Expand All @@ -125,8 +137,7 @@ func TestSILCommsClient_MakeRequest(t *testing.T) {
wantErr: false,
},
{
name: "happy case: make unauthenticated request",
fields: fields{},
name: "happy case: make unauthenticated request",
args: args{
ctx: context.Background(),
method: http.MethodPost,
Expand All @@ -142,6 +153,7 @@ func TestSILCommsClient_MakeRequest(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
httpmock.Activate()
defer httpmock.DeactivateAndReset()
MockLogin()

httpmock.RegisterResponder(http.MethodPost, "/v1/sms/bulk/", func(r *http.Request) (*http.Response, error) {
resp := APIResponse{
Expand Down Expand Up @@ -175,17 +187,14 @@ func TestSILCommsClient_MakeRequest(t *testing.T) {
})
}

s := &CommsClient{
client: tt.fields.client,
accessToken: tt.fields.accessToken,
}
s := newClient()
got, err := s.MakeRequest(tt.args.ctx, tt.args.method, tt.args.path, tt.args.queryParams, tt.args.body, tt.args.authorised)
if (err != nil) != tt.wantErr {
t.Errorf("SILCommsClient.MakeRequest() error = %v, wantErr %v", err, tt.wantErr)
t.Errorf("SILclient.MakeRequest() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantErr && got == nil {
t.Errorf("SILCommsClient.MakeRequest() expected response not to be nil for %v", tt.name)
t.Errorf("SILclient.MakeRequest() expected response not to be nil for %v", tt.name)
return
}
})
Expand Down
21 changes: 9 additions & 12 deletions sms.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,21 @@ import (
)

var (
// commsSenderID is the ID used to send the SMS
commsSenderID = serverutils.MustGetEnvVar("SIL_COMMS_SENDER_ID")
// SenderID is the ID used to send the SMS
SenderID = serverutils.MustGetEnvVar("SIL_COMMS_SENDER_ID")
)

// ICommsClient is the interface for the client to make API request to sil communications
type ICommsClient interface {
MakeRequest(ctx context.Context, method, path string, queryParams map[string]string, body interface{}, authorised bool) (*http.Response, error)
}

// CommsLib is the SDK implementation for interacting with the sil communications API
type CommsLib struct {
Client ICommsClient
client *client
}

// NewSILCommsLib initializes a new implementation of the SIL Comms SDK
func NewSILCommsLib(client ICommsClient) *CommsLib {
func NewSILCommsLib() *CommsLib {
client := newClient()

l := &CommsLib{
Client: client,
client: client,
}

return l
Expand All @@ -44,12 +41,12 @@ func (l CommsLib) SendBulkSMS(ctx context.Context, message string, recipients []
Message string `json:"message"`
Recipients []string `json:"recipients"`
}{
Sender: commsSenderID,
Sender: SenderID,
Message: message,
Recipients: recipients,
}

response, err := l.Client.MakeRequest(ctx, http.MethodPost, path, nil, payload, true)
response, err := l.client.MakeRequest(ctx, http.MethodPost, path, nil, payload, true)
if err != nil {
return nil, fmt.Errorf("failed to make send bulk sms request: %w", err)
}
Expand Down
42 changes: 12 additions & 30 deletions sms_test.go
Original file line number Diff line number Diff line change
@@ -1,27 +1,16 @@
package silcomms_test

import (
"bytes"
"context"
"encoding/json"
"io"
"fmt"
"net/http"
"testing"

"github.com/brianvoe/gofakeit"
"github.com/jarcoal/httpmock"
"github.com/savannahghi/silcomms"
)

// ClientMock ...
type ClientMock struct {
MockMakeRequestFn func(ctx context.Context, method, path string, queryParams map[string]string, body interface{}, authorised bool) (*http.Response, error)
}

// MakeRequest ...
func (c *ClientMock) MakeRequest(ctx context.Context, method, path string, queryParams map[string]string, body interface{}, authorised bool) (*http.Response, error) {
return c.MockMakeRequestFn(ctx, method, path, queryParams, body, authorised)
}

func TestSILCommsLib_SendBulkSMS(t *testing.T) {
type args struct {
ctx context.Context
Expand All @@ -47,30 +36,23 @@ func TestSILCommsLib_SendBulkSMS(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client := &ClientMock{}
l := silcomms.NewSILCommsLib(client)
httpmock.Activate()
defer httpmock.DeactivateAndReset()
silcomms.MockLogin()

l := silcomms.NewSILCommsLib()

if tt.name == "happy case: send bulk sms" {
client.MockMakeRequestFn = func(ctx context.Context, method, path string, queryParams map[string]string, body interface{}, authorised bool) (*http.Response, error) {
msg := silcomms.APIResponse{
httpmock.RegisterResponder(http.MethodPost, fmt.Sprintf("%s/v1/sms/bulk/", silcomms.BaseURL), func(r *http.Request) (*http.Response, error) {
resp := silcomms.APIResponse{
Status: silcomms.StatusSuccess,
Message: "success",
Data: silcomms.BulkSMSResponse{
GUID: "",
Sender: "",
Message: "",
Recipients: []string{},
State: "",
SMS: []string{},
Created: "",
Updated: "",
GUID: gofakeit.UUID(),
},
}

payload, _ := json.Marshal(msg)

return &http.Response{StatusCode: http.StatusAccepted, Body: io.NopCloser(bytes.NewBuffer(payload))}, nil
}
return httpmock.NewJsonResponse(http.StatusAccepted, resp)
})
}

got, err := l.SendBulkSMS(tt.args.ctx, tt.args.message, tt.args.recipients)
Expand Down

0 comments on commit a7a89b5

Please sign in to comment.