diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 54295a7..3576bfe 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -11,8 +11,42 @@ on: jobs: + lint: + name: Linting + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v3 + + - name: Setup Go + uses: actions/setup-go@v4 + with: + go-version: '1.21.1' + cache: false + + - name: Lint and Vet + uses: golangci/golangci-lint-action@v3 + with: + version: latest + + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.21.1' + + - name: Test + run: go test -v ./... --cover + build: runs-on: ubuntu-latest + needs: + - lint + - test steps: - uses: actions/checkout@v3 @@ -24,5 +58,3 @@ jobs: - name: Build run: go build -v ./... - - name: Test - run: go test -v ./... diff --git a/fastotp.go b/fastotp.go index 999e60c..a891c07 100644 --- a/fastotp.go +++ b/fastotp.go @@ -10,19 +10,23 @@ import ( ) const ( - BaseURL = "https://api.fastotp.co" + baseURL = "https://api.fastotp.co" ) -type FastOtp struct { - APIKey *string +// FastOTP is the main struct for the FastOtp package. +type FastOTP struct { + APIKey string BaseURL string + client HttpClient } +// ErrorResponse is the error struct for the FastOtp package. type ErrorResponse struct { Errors map[string][]string `json:"errors"` Message string `json:"message"` } +// OTP is the struct for the OTP object. type OTP struct { CreatedAt time.Time `json:"created_at"` ExpiresAt time.Time `json:"expires_at"` @@ -30,41 +34,51 @@ type OTP struct { DeliveryDetails DeliveryDetails `json:"delivery_details"` ID string `json:"id"` Identifier string `json:"identifier"` - Status string `json:"status"` - Type string `json:"type"` + Status OTPStatus `json:"status"` + Type OTPType `json:"type"` DeliveryMethods []string `json:"delivery_methods"` } +// OTPResponse is the struct for the OTP response object. type OTPResponse struct { OTP OTP `json:"otp"` } +// DeliveryDetails is the struct for the DeliveryDetails object. type DeliveryDetails struct { Email string `json:"email"` } -type OtpDelivery map[string]string +// OTPDelivery is the struct for the OtpDelivery object. +type OTPDelivery map[string]string +// GenerateOTPPayload is the struct for the GenerateOTPPayload object. type GenerateOTPPayload struct { - Delivery OtpDelivery `json:"delivery"` + Delivery OTPDelivery `json:"delivery"` Identifier string `json:"identifier"` - Type string `json:"type"` + Type OTPType `json:"type"` TokenLength int `json:"token_length"` Validity int `json:"validity"` } +// ValidateOTPPayload is the struct for the ValidateOTPPayload object. type ValidateOTPPayload struct { Identifier string `json:"identifier"` Token string `json:"token"` } -func NewFastOTP(apiKey string) *FastOtp { - return &FastOtp{APIKey: &apiKey, BaseURL: BaseURL} +// NewFastOTP creates a new FastOtp instance. +func NewFastOTP(apiKey string) *FastOTP { + return &FastOTP{ + APIKey: apiKey, + BaseURL: baseURL, + client: httpclient.NewAPIClient(baseURL, apiKey), + } } -func (f *FastOtp) GenerateOTP(payload GenerateOTPPayload) (*OTP, error) { - cl := httpclient.NewAPIClient(f.BaseURL, *f.APIKey) - resp, err := cl.Post("/generate", payload) +// GenerateOTP generates an OTP. +func (f *FastOTP) GenerateOTP(payload GenerateOTPPayload) (*OTP, error) { + resp, err := f.client.Post("/generate", payload) if err != nil { return nil, err } @@ -91,9 +105,9 @@ func (f *FastOtp) GenerateOTP(payload GenerateOTPPayload) (*OTP, error) { return &otpResponse.OTP, nil } -func (f *FastOtp) ValidateOTP(payload ValidateOTPPayload) (*OTP, error) { - cl := httpclient.NewAPIClient(f.BaseURL, *f.APIKey) - resp, err := cl.Post("/validate", payload) +// ValidateOTP validates an OTP. +func (f *FastOTP) ValidateOTP(payload ValidateOTPPayload) (*OTP, error) { + resp, err := f.client.Post("/validate", payload) if err != nil { return nil, err } @@ -120,11 +134,10 @@ func (f *FastOtp) ValidateOTP(payload ValidateOTPPayload) (*OTP, error) { return &otpResponse.OTP, nil } -func (f *FastOtp) GetOtp(id string) (*OTP, error) { - cl := httpclient.NewAPIClient(f.BaseURL, *f.APIKey) - resp, err := cl.Get(id) +// GetOtp gets a new otp +func (f *FastOTP) GetOtp(id string) (*OTP, error) { + resp, err := f.client.Get(id) if err != nil { - fmt.Println("got here") return nil, err } defer resp.Body.Close() diff --git a/fastotp_test.go b/fastotp_test.go index 26fad5d..90b1f66 100644 --- a/fastotp_test.go +++ b/fastotp_test.go @@ -1,49 +1,153 @@ package fastotp import ( + "bytes" + "encoding/json" + "errors" "fmt" - "io" + "log" "net/http" - "net/http/httptest" + "os" "testing" + + httpclient "github.com/CeoFred/fast-otp/lib" + + "github.com/jarcoal/httpmock" + "github.com/stretchr/testify/assert" + "gopkg.in/stretchr/testify.v1/require" ) -func TestGenerateOTP(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - t.Errorf("Expected POST request, got %s", r.Method) +var ( + mockedResponse = `{ + "otp": { + "created_at": "2024-01-19T00:24:06.000000Z", + "delivery_details": { + "email": "test@example.com" + }, + "delivery_methods": [ + "email" + ], + "expires_at": "2024-01-19T17:04:06.000000Z", + "id": "9b202659-fee7-46ab-836b-cdd310c4f327", + "identifier": "test_identifier", + "status": "pending", + "type": "alpha_numeric", + "updated_at": "2024-01-19T00:24:06.000000Z" } - if r.URL.Path != "/generate" { - t.Errorf("Expected path /generate, got %s", r.URL.Path) + }` + + mockedValidationResponse = `{ + "otp": { + "created_at": "2024-01-19T00:24:06.000000Z", + "delivery_details": { + "email": "test@example.com" + }, + "delivery_methods": [ + "email" + ], + "expires_at": "2024-01-19T17:04:06.000000Z", + "id": "9b202659-fee7-46ab-836b-cdd310c4f327", + "identifier": "test_identifier", + "status": "validated", + "type": "alpha_numeric", + "updated_at": "2024-01-19T00:24:06.000000Z" } + }` + + mockedError = `{ + "message": "something isn't right" + }` + + mockedErrorWithSomeError = `{ + "message": "something isn't right" + "errors":[ + "some error" + ] + }` + + mockAPIKey = "test_api_key" +) + +func TestMain(m *testing.M) { + code := 1 + defer func() { + os.Exit(code) + }() + + code = m.Run() +} + +func TestMarshalling(t *testing.T) { + var resp *OTPResponse + err := json.NewDecoder(bytes.NewBuffer([]byte(mockedResponse))).Decode(&resp) + require.NoError(t, err) + + fmt.Printf("\n\nID is: %s\n\n", resp.OTP.ID) + assert.Equal(t, "test@example.com", resp.OTP.DeliveryDetails.Email) + assert.Equal(t, OTPStatusPending, resp.OTP.Status) + assert.Equal(t, OTPTypeAlphaNumeric, resp.OTP.Type) + assert.Equal(t, "9b202659-fee7-46ab-836b-cdd310c4f327", resp.OTP.ID) + assert.Equal(t, "test_identifier", resp.OTP.Identifier) +} + +func TestGenerateOTP(t *testing.T) { + reset := mockHttpRequest(http.StatusOK, http.MethodPost, "/generate", mockedResponse) + defer reset() + fastOtp := NewFastOTP(mockAPIKey) + + delivery := OTPDelivery{ + "email": "test@example.com", + } + + payload := GenerateOTPPayload{ + Delivery: delivery, + Identifier: "test_identifier", + TokenLength: 6, + Type: OTPTypeAlphaNumeric, + Validity: 120, + } + + otp, err := fastOtp.GenerateOTP(payload) + require.NoError(t, err) + require.NotNil(t, otp) + + assert.Equal(t, "test@example.com", otp.DeliveryDetails.Email) + assert.Equal(t, "test_identifier", otp.Identifier) + assert.Equal(t, OTPStatusPending, otp.Status) + assert.Equal(t, OTPTypeAlphaNumeric, otp.Type) + assert.Equal(t, "9b202659-fee7-46ab-836b-cdd310c4f327", otp.ID) +} + +func TestGenerateOTP_500Response(t *testing.T) { + reset := mockErrorHttpRequest(500, http.MethodPost, "/generate", "") + defer reset() + + fastOtp := NewFastOTP(mockAPIKey) + + delivery := OTPDelivery{ + "email": "test@example.com", + } + + payload := GenerateOTPPayload{ + Delivery: delivery, + Identifier: "test_identifier", + TokenLength: 6, + Type: OTPTypeAlphaNumeric, + Validity: 120, + } + + otp, err := fastOtp.GenerateOTP(payload) + require.Error(t, err) + assert.Nil(t, otp) +} - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(`{ - "otp": { - "created_at": "2024-01-19T00:24:06.000000Z", - "delivery_details": { - "email": "test@example.com" - }, - "delivery_methods": [ - "email" - ], - "expires_at": "2024-01-19T17:04:06.000000Z", - "id": "9b202659-fee7-46ab-836b-cdd310c4f327", - "identifier": "test_identifier", - "status": "pending", - "type": "alpha_numeric", - "updated_at": "2024-01-19T00:24:06.000000Z" - } - }`)) - - body, _ := io.ReadAll(r.Body) - fmt.Println("Request Body:", string(body)) - })) - defer server.Close() - - fastOtp := &FastOtp{APIKey: new(string), BaseURL: server.URL} - - delivery := OtpDelivery{ +func TestGenerateOTP_400Response(t *testing.T) { + reset := mockErrorHttpRequest(http.StatusBadRequest, http.MethodPost, "/generate", mockedError) + defer reset() + + fastOtp := NewFastOTP(mockAPIKey) + + delivery := OTPDelivery{ "email": "test@example.com", } @@ -51,58 +155,144 @@ func TestGenerateOTP(t *testing.T) { Delivery: delivery, Identifier: "test_identifier", TokenLength: 6, - Type: "alpha_numeric", + Type: OTPTypeAlphaNumeric, Validity: 120, } otp, err := fastOtp.GenerateOTP(payload) - if err != nil { - t.Errorf("Unexpected error: %v", err) + require.Error(t, err) + assert.Nil(t, otp) + assert.Contains(t, err.Error(), "something isn't right") +} + +func TestGenerateOTP_401ErrorResponse(t *testing.T) { + fastOtp := &FastOTP{ + client: mockedHTTPClient{ + GetFunc: func(id string) (*http.Response, error) { + var otpResponse *OTPResponse + err := json.NewDecoder(bytes.NewBuffer([]byte(mockedValidationResponse))).Decode(&otpResponse) + if err != nil { + log.Fatal(err) + } + return httpmock.NewJsonResponse(401, + &ErrorResponse{ + Errors: map[string][]string{"error": {"something isn't right"}}, + Message: "something isn't right"}) + }, + PostFunc: func(endpoint string, payload interface{}) (*http.Response, error) { + return nil, errors.New("something isn't right") + }, + }, + } + delivery := OTPDelivery{ + "email": "test@example.com", + } + + payload := GenerateOTPPayload{ + Delivery: delivery, + Identifier: "test_identifier", + TokenLength: 6, + Type: OTPTypeAlphaNumeric, + Validity: 120, } - fmt.Println("Generated OTP:", otp) - if otp.DeliveryDetails.Email != "test@example.com" { - t.Errorf("Expected email: %s, got %s", "test@example.com", otp.DeliveryDetails.Email) + otp, err := fastOtp.GenerateOTP(payload) + require.Error(t, err) + require.Nil(t, otp) +} + +func TestGenerateOTP_401MoreErrorResponse(t *testing.T) { + fastOtp := &FastOTP{ + client: mockedHTTPClient{ + PostFunc: func(endpoint string, payload interface{}) (*http.Response, error) { + return httpmock.NewJsonResponse(401, + &ErrorResponse{ + Errors: map[string][]string{"error": {"something isn't right"}}, + Message: "something isn't right"}) + }, + }, + } + delivery := OTPDelivery{ + "email": "test@example.com", } - if otp.Identifier != "test_identifier" { - t.Errorf("Expected identifier: %s, got %s", "test_identifier", otp.Identifier) + + payload := GenerateOTPPayload{ + Delivery: delivery, + Identifier: "test_identifier", + TokenLength: 6, + Type: OTPTypeAlphaNumeric, + Validity: 120, } + + otp, err := fastOtp.GenerateOTP(payload) + require.Error(t, err) + require.Nil(t, otp) +} + +func TestGenerateOTP_401Response(t *testing.T) { + reset := mockErrorHttpRequest(http.StatusBadRequest, http.MethodPost, "/generate", mockedErrorWithSomeError) + defer reset() + + fastOtp := NewFastOTP(mockAPIKey) + + delivery := OTPDelivery{ + "email": "test@example.com", + } + + payload := GenerateOTPPayload{ + Delivery: delivery, + Identifier: "test_identifier", + TokenLength: 6, + Type: OTPTypeAlphaNumeric, + Validity: 120, + } + + otp, err := fastOtp.GenerateOTP(payload) + require.Error(t, err) + assert.Nil(t, otp) + assert.Contains(t, err.Error(), "something isn't right") + } func TestValidateOTP(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - t.Errorf("Expected POST request, got %s", r.Method) - } - if r.URL.Path != "/validate" { - t.Errorf("Expected path /validate, got %s", r.URL.Path) - } + reset := mockHttpRequest(http.StatusOK, http.MethodPost, "/validate", mockedValidationResponse) + defer reset() + + fastOtp := NewFastOTP(mockAPIKey) + + payload := ValidateOTPPayload{ + Identifier: "test_identifier", + Token: "123456", + } + + otp, err := fastOtp.ValidateOTP(payload) + require.NoError(t, err) + require.NotNil(t, otp) + assert.Equal(t, "test_identifier", otp.Identifier) + assert.Equal(t, OTPStatusValidated, otp.Status) +} + +func TestValidateOTP_500Response(t *testing.T) { + reset := mockErrorHttpRequest(500, http.MethodPost, "/validate", "") + defer reset() + + fastOtp := NewFastOTP(mockAPIKey) - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(`{ - "otp": { - "created_at": "2024-01-19T00:24:06.000000Z", - "delivery_details": { - "email": "test@example.com" - }, - "delivery_methods": [ - "email" - ], - "expires_at": "2024-01-19T17:04:06.000000Z", - "id": "9b202659-fee7-46ab-836b-cdd310c4f327", - "identifier": "test_identifier", - "status": "validated", - "type": "alpha_numeric", - "updated_at": "2024-01-19T00:24:06.000000Z" - } - }`)) - - body, _ := io.ReadAll(r.Body) - fmt.Println("Request Body:", string(body)) - })) - defer server.Close() - - fastOtp := &FastOtp{APIKey: new(string), BaseURL: server.URL} + payload := ValidateOTPPayload{ + Identifier: "test_identifier", + Token: "123456", + } + + otp, err := fastOtp.ValidateOTP(payload) + require.Error(t, err) + assert.Nil(t, otp) + +} + +func TestValidateOTP_400Response(t *testing.T) { + reset := mockErrorHttpRequest(http.StatusBadRequest, http.MethodPost, "/validate", mockedError) + defer reset() + fastOtp := NewFastOTP(mockAPIKey) payload := ValidateOTPPayload{ Identifier: "test_identifier", @@ -110,11 +300,174 @@ func TestValidateOTP(t *testing.T) { } otp, err := fastOtp.ValidateOTP(payload) - if err != nil { - t.Errorf("Unexpected error: %v", err) + require.Error(t, err) + assert.Nil(t, otp) +} + +func TestValidateOTP_401Response(t *testing.T) { + reset := mockErrorHttpRequest(http.StatusUnauthorized, http.MethodPost, "/validate", mockedErrorWithSomeError) + defer reset() + + fastOtp := NewFastOTP(mockAPIKey) + + payload := ValidateOTPPayload{ + Identifier: "test_identifier", + Token: "123456", } - if otp.Status != "validated" { - t.Errorf("Expected OTP status to be: %s, got %s", "validated", otp.DeliveryDetails.Email) + otp, err := fastOtp.ValidateOTP(payload) + require.Error(t, err) + assert.Nil(t, otp) +} + +func TestValidateOTP_401MoreErrorResponse(t *testing.T) { + fastOtp := &FastOTP{ + client: mockedHTTPClient{ + PostFunc: func(endpoint string, payload interface{}) (*http.Response, error) { + return httpmock.NewJsonResponse(401, + &ErrorResponse{ + Errors: map[string][]string{"error": {"something isn't right"}}, + Message: "something isn't right"}) + }, + }, } + + payload := ValidateOTPPayload{ + Identifier: "test_identifier", + Token: "123456", + } + + otp, err := fastOtp.ValidateOTP(payload) + require.Error(t, err) + assert.Nil(t, otp) +} + +func TestValidateOTP_POSTError(t *testing.T) { + fastOtp := &FastOTP{ + client: mockedHTTPClient{ + PostFunc: func(endpoint string, payload interface{}) (*http.Response, error) { + return nil, errors.New("some error") + }, + }, + } + + payload := ValidateOTPPayload{ + Identifier: "test_identifier", + Token: "123456", + } + + otp, err := fastOtp.ValidateOTP(payload) + require.EqualError(t, err, "some error") + assert.Nil(t, otp) +} + +func TestFastOtp_GetOtp(t *testing.T) { + fastOtp := &FastOTP{ + APIKey: "", + BaseURL: "", + client: mockedHTTPClient{ + GetFunc: func(id string) (*http.Response, error) { + var otpResponse *OTPResponse + err := json.NewDecoder(bytes.NewBuffer([]byte(mockedValidationResponse))).Decode(&otpResponse) + if err != nil { + log.Fatal(err) + } + return httpmock.NewJsonResponse(200, otpResponse) + }, + }, + } + + otp, err := fastOtp.GetOtp("test") + require.NoError(t, err) + require.NotNil(t, otp) + + //assert.Equal(t, "123456", otp.T) + assert.Equal(t, OTPStatusValidated, otp.Status) +} + +func TestFastOtp_GetOtpWithError(t *testing.T) { + fastOtp := &FastOTP{ + client: mockedHTTPClient{ + GetFunc: func(id string) (*http.Response, error) { + var otpResponse *OTPResponse + err := json.NewDecoder(bytes.NewBuffer([]byte(mockedValidationResponse))).Decode(&otpResponse) + if err != nil { + log.Fatal(err) + } + return httpmock.NewJsonResponse(401, &ErrorResponse{Message: "something isn't right"}) + }, + }, + } + + otp, err := fastOtp.GetOtp("test") + require.Error(t, err) + require.Nil(t, otp) +} + +func TestFastOtp_GetOtpWithMoreError(t *testing.T) { + fastOtp := &FastOTP{ + client: mockedHTTPClient{ + GetFunc: func(id string) (*http.Response, error) { + var otpResponse *OTPResponse + err := json.NewDecoder(bytes.NewBuffer([]byte(mockedValidationResponse))).Decode(&otpResponse) + if err != nil { + log.Fatal(err) + } + return httpmock.NewJsonResponse(401, + &ErrorResponse{ + Errors: map[string][]string{"error": {"something isn't right"}}, + Message: "something isn't right"}) + }, + }, + } + + otp, err := fastOtp.GetOtp("test") + require.Error(t, err) + require.Nil(t, otp) +} + +func mockHttpRequest(code int, method, path, response string) func() { + httpmock.ActivateNonDefault(httpclient.FastOTPClient) + httpmock.RegisterResponder(method, path, func(req *http.Request) (*http.Response, error) { + // this can be used for method/path specific assertions + // if req.Method == http.MethodPost { + // body, err := io.ReadAll(req.Body) + // if err != nil { + // log.Fatal(err) + // } + // } + + var otpResponse *OTPResponse + err := json.NewDecoder(bytes.NewBuffer([]byte(response))).Decode(&otpResponse) + if err != nil { + log.Fatalln(err) + } + + return httpmock.NewJsonResponse(code, otpResponse) + }) + return httpmock.DeactivateAndReset +} + +func mockErrorHttpRequest(code int, method, path, response string) func() { + httpmock.ActivateNonDefault(httpclient.FastOTPClient) + httpmock.RegisterResponder(method, path, func(req *http.Request) (*http.Response, error) { + // this can be used for method/path specific assertions + // if req.Method == http.MethodPost { + // body, err := io.ReadAll(req.Body) + // if err != nil { + // log.Fatal(err) + // } + // } + + var ( + errorResponse ErrorResponse + ) + err := json.NewDecoder(bytes.NewBuffer([]byte(mockedError))).Decode(&errorResponse) + if err != nil { + return httpmock.NewStringResponse(http.StatusInternalServerError, mockedError), nil + } + + return httpmock.NewJsonResponse(code, errorResponse) + }) + return httpmock.DeactivateAndReset } diff --git a/go.mod b/go.mod index 49f2e23..8fec9de 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,15 @@ module github.com/CeoFred/fast-otp go 1.21.2 + +require ( + github.com/jarcoal/httpmock v1.3.1 + github.com/stretchr/testify v1.8.4 + gopkg.in/stretchr/testify.v1 v1.2.2 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..c0cce8b --- /dev/null +++ b/go.sum @@ -0,0 +1,16 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww= +github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= +github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g= +github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/stretchr/testify.v1 v1.2.2 h1:yhQC6Uy5CqibAIlk1wlusa/MJ3iAN49/BsR/dCCKz3M= +gopkg.in/stretchr/testify.v1 v1.2.2/go.mod h1:QI5V/q6UbPmuhtm10CaFZxED9NreB8PnFYN9JcR6TxU= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/interface.go b/interface.go new file mode 100644 index 0000000..4a332ec --- /dev/null +++ b/interface.go @@ -0,0 +1,11 @@ +package fastotp + +import ( + "net/http" +) + +// HttpClient is the interface for the HTTP client. +type HttpClient interface { + Get(id string) (*http.Response, error) + Post(endpoint string, payload interface{}) (*http.Response, error) +} diff --git a/lib/http.go b/lib/http.go index d9dd649..02b86c0 100644 --- a/lib/http.go +++ b/lib/http.go @@ -5,25 +5,39 @@ import ( "encoding/json" "fmt" "net/http" + "time" +) + +var ( + FastOTPClient = &http.Client{ + Timeout: time.Duration(5) * time.Second, + Transport: &http.Transport{ + MaxIdleConns: 10, + MaxIdleConnsPerHost: 100, + IdleConnTimeout: 30 * time.Second, + }, + } ) // APIClient is a wrapper for making HTTP requests to the fastotp API. type APIClient struct { - BaseURL string - APIKey string + baseURL string + apiKey string + client *http.Client } // NewAPIClient creates a new instance of APIClient. func NewAPIClient(baseURL, apiKey string) *APIClient { return &APIClient{ - BaseURL: baseURL, - APIKey: apiKey, + baseURL: baseURL, + apiKey: apiKey, + client: FastOTPClient, } } // Post sends a POST request to the specified endpoint with the given payload. func (c *APIClient) Post(endpoint string, payload interface{}) (*http.Response, error) { - url := c.BaseURL + endpoint + url := c.baseURL + endpoint // Convert payload to JSON payloadBytes, err := json.Marshal(payload) @@ -37,16 +51,14 @@ func (c *APIClient) Post(endpoint string, payload interface{}) (*http.Response, } req.Header.Set("Content-Type", "application/json") - req.Header.Set("x-api-key", c.APIKey) + req.Header.Set("x-api-key", c.apiKey) - client := http.DefaultClient - return client.Do(req) + return c.client.Do(req) } // Get sends a GET request to the specified endpoint, appending id as a path parameter func (c *APIClient) Get(id string) (*http.Response, error) { - url := fmt.Sprintf("%s/%s", c.BaseURL, id) - fmt.Println(url) + url := fmt.Sprintf("%s/%s", c.baseURL, id) req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { @@ -54,8 +66,7 @@ func (c *APIClient) Get(id string) (*http.Response, error) { } req.Header.Set("Content-Type", "application/json") - req.Header.Set("x-api-key", c.APIKey) + req.Header.Set("x-api-key", c.apiKey) - client := http.DefaultClient - return client.Do(req) + return c.client.Do(req) } diff --git a/mock.go b/mock.go new file mode 100644 index 0000000..664ac05 --- /dev/null +++ b/mock.go @@ -0,0 +1,18 @@ +package fastotp + +import ( + "net/http" +) + +type mockedHTTPClient struct { + GetFunc func(id string) (*http.Response, error) + PostFunc func(endpoint string, payload interface{}) (*http.Response, error) +} + +func (m mockedHTTPClient) Get(id string) (*http.Response, error) { + return m.GetFunc(id) +} + +func (m mockedHTTPClient) Post(endpoint string, payload interface{}) (*http.Response, error) { + return m.PostFunc(endpoint, payload) +} diff --git a/type_test.go b/type_test.go new file mode 100644 index 0000000..d6c08e1 --- /dev/null +++ b/type_test.go @@ -0,0 +1,61 @@ +package fastotp + +import "testing" + +func TestOTPType_String(t *testing.T) { + tests := []struct { + name string + o OTPType + want string + }{ + { + name: "TestOTPType_String", + o: OTPTypeNumeric, + want: "numeric", + }, + { + name: "TestOTPType_String", + o: OTPTypeAlpha, + want: "alpha", + }, + { + name: "TestOTPType_String", + o: OTPTypeAlphaNumeric, + want: "alpha_numeric", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.o.String(); got != tt.want { + t.Errorf("OTPType.String() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestOTPStatus_String(t *testing.T) { + tests := []struct { + name string + o OTPStatus + want string + }{ + { + name: "TestOTPStatus_String", + o: OTPStatusPending, + want: "pending", + }, + { + name: "TestOTPStatus_String", + o: OTPStatusValidated, + want: "validated", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.o.String(); got != tt.want { + t.Errorf("OTPStatus.String() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/types.go b/types.go new file mode 100644 index 0000000..7fb506d --- /dev/null +++ b/types.go @@ -0,0 +1,34 @@ +package fastotp + +type ( + + // OTPType otp types + OTPType string + + // OTPStatus status of otp + OTPStatus string +) + +const ( + OTPTypeUnknown OTPType = "unknown" + // OTPTypeNumeric numbers only OTP + OTPTypeNumeric OTPType = "numeric" + // OTPTypeAlpha alphabet only OTP + OTPTypeAlpha OTPType = "alpha" + // OTPTypeAlphaNumeric combination of numbers and alphabet OTP + OTPTypeAlphaNumeric OTPType = "alpha_numeric" + + // OTPStatusPending pending otp status + OTPStatusPending OTPStatus = "pending" + // OTPStatusValidated validated otp status + OTPStatusValidated OTPStatus = "validated" +) + +// String returns the string value of OTPType +func (o OTPType) String() string { + return string(o) +} + +func (o OTPStatus) String() string { + return string(o) +}