Skip to content

Commit

Permalink
Merge pull request #24 from koladev32/19-add-tests-suite-for-the-webh…
Browse files Browse the repository at this point in the history
…ook-sender-package

19 add tests suite for the webhook sender package
  • Loading branch information
koladev32 authored Sep 10, 2023
2 parents bab4271 + c59dd49 commit 4f31a81
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 9 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

### Added

- Add tests suite for the Webhook sender package (#19)
- Refactor ProcessWebhooks for clarity, safety, and testability (#3 )
- Implement Payload Signing with webhook-signature Header in SendWebhook (#6)
- Implement File Logging Module for Enhanced Error Handling (#7)
Expand Down
4 changes: 2 additions & 2 deletions webhook/logging/logging.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ func currentDateTime() string {
return time.Now().Format("2006-01-02 15:04:05")
}

func WebhookLogger(errorType string, errorMessage error) error {
var WebhookLogger = func(errorType string, errorMessage error) error {
logFileDate := currentDate()
logFileName := fmt.Sprintf("../logs/%s.log", logFileDate)
logFileName := fmt.Sprintf("%s.log", logFileDate)

file, err := os.OpenFile(logFileName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion webhook/queue/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func retryWithExponentialBackoff(payload redisClient.WebhookPayload) error {
time.Sleep(backoffTime)
}

logging.WebhookLogger(logging.WarningType, fmt.Errorf("maximum retries reached: %s", maxRetries))
logging.WebhookLogger(logging.WarningType, fmt.Errorf("maximum retries reached: %d", maxRetries))

return nil
}
81 changes: 81 additions & 0 deletions webhook/sender/sender_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package sender

/*
This package contains mostly test functions for the utils used to send webhooks. If these utils works,
we can ensure that the send webhook function will function normally too. For a whole test suite for the sender function,
check out the webhook_test.go file.
*/

import (
"bytes"
"io"
"net/http"
"testing"
)

type MockClient struct {
MockDo func(req *http.Request) (*http.Response, error)
}

func (m *MockClient) Do(req *http.Request) (*http.Response, error) {
return m.MockDo(req)
}

// Testing the MarshalJSON method
func TestMarshalJSON(t *testing.T) {
data := map[string]string{
"key": "value",
}
expectedJSON := `{"key":"value"}`

jsonBytes, err := marshalJSON(data)
if err != nil {
t.Fatalf("Expected no error, but got: %v", err)
}

if string(jsonBytes) != expectedJSON {
t.Fatalf("Expected %s, but got %s", expectedJSON, string(jsonBytes))
}
}

// Test for prepareRequest
func TestPrepareRequest(t *testing.T) {
url := "http://example.com/webhook"
jsonBytes := []byte(`{"key":"value"}`)
secretHash := "secret123"

req, err := prepareRequest(url, jsonBytes, secretHash)
if err != nil {
t.Fatalf("Expected no error, but got: %v", err)
}

if req.Header.Get("Content-Type") != "application/json" {
t.Fatalf("Expected header Content-Type to be application/json but got %s", req.Header.Get("Content-Type"))
}

if req.Header.Get("X-Secret-Hash") != secretHash {
t.Fatalf("Expected header X-Secret-Hash to be %s but got %s", secretHash, req.Header.Get("X-Secret-Hash"))
}
}

func TestSendRequest(t *testing.T) {
HTTPClient = &MockClient{
MockDo: func(req *http.Request) (*http.Response, error) {
return &http.Response{
StatusCode: 200,
Body: io.NopCloser(bytes.NewBufferString("OK")),
}, nil
},
}

req, _ := http.NewRequest("GET", "http://example.com", nil)
resp, err := sendRequest(req)
if err != nil {
t.Fatalf("Expected no error, but got: %v", err)
}

body, _ := io.ReadAll(resp.Body)
if string(body) != "OK" {
t.Fatalf("Expected body to be OK but got %s", string(body))
}
}
16 changes: 10 additions & 6 deletions webhook/sender/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@ import (
"webhook/logging"
)

var HTTPClient = &http.Client{}
type HTTPDoer interface {
Do(req *http.Request) (*http.Response, error)
}

var HTTPClient HTTPDoer = &http.Client{}

func marshalJSON(data interface{}) ([]byte, error) {
var marshalJSON = func(data interface{}) ([]byte, error) {
jsonBytes, err := json.Marshal(data)
if err != nil {
logging.WebhookLogger(logging.ErrorType, fmt.Errorf("error marshaling JSON: %s", err))
Expand All @@ -20,7 +24,7 @@ func marshalJSON(data interface{}) ([]byte, error) {
return jsonBytes, nil
}

func prepareRequest(url string, jsonBytes []byte, secretHash string) (*http.Request, error) {
var prepareRequest = func(url string, jsonBytes []byte, secretHash string) (*http.Request, error) {
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonBytes))
if err != nil {
logging.WebhookLogger(logging.ErrorType, fmt.Errorf("error during the webhook request preparation"))
Expand All @@ -36,21 +40,21 @@ func prepareRequest(url string, jsonBytes []byte, secretHash string) (*http.Requ
return req, nil
}

func sendRequest(req *http.Request) (*http.Response, error) {
var sendRequest = func(req *http.Request) (*http.Response, error) {
resp, err := HTTPClient.Do(req)
if err != nil {
return nil, err
}
return resp, nil
}

func closeResponse(body io.ReadCloser) {
var closeResponse = func(body io.ReadCloser) {
if err := body.Close(); err != nil {
logging.WebhookLogger(logging.ErrorType, fmt.Errorf("error closing response body: %s", err))
}
}

func processResponse(resp *http.Response) (string, []byte, error) {
var processResponse = func(resp *http.Response) (string, []byte, error) {
respBody, err := io.ReadAll(resp.Body)
if err != nil {
logging.WebhookLogger(logging.ErrorType, fmt.Errorf("error reading response body: %s", err))
Expand Down
92 changes: 92 additions & 0 deletions webhook/sender/webhook_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package sender

import (
"errors"
"net/http"
"testing"
"webhook/logging"
)

var (
marshalJSONOrig = marshalJSON
prepareRequestOrig = prepareRequest
sendRequestOrig = sendRequest
processResponseOrig = processResponse
webhookLoggerInvoked = false
)

func TestSendWebhook(t *testing.T) {
logging.WebhookLogger = func(errorType string, errorMessage error) error {
webhookLoggerInvoked = true
return nil
}

t.Run("Successful webhook sending", func(t *testing.T) {
resetMocks() // Reset all mocks to original functions

err := SendWebhook(nil, "http://dummy.com", "webhookId", "secretHash")
if err != nil {
t.Fatalf("Expected no error, but got: %v", err)
}
})

t.Run("Failed webhook due to marshaling errors", func(t *testing.T) {
resetMocks()
marshalJSON = func(data interface{}) ([]byte, error) {
return nil, errors.New("marshaling error")
}

err := SendWebhook(nil, "http://dummy.com", "webhookId", "secretHash")
if err == nil || err.Error() != "marshaling error" {
t.Fatalf("Expected marshaling error, but got: %v", err)
}
})

t.Run("Failed webhook due to request preparation errors", func(t *testing.T) {
resetMocks()
prepareRequest = func(url string, jsonBytes []byte, secretHash string) (*http.Request, error) {
return nil, errors.New("request preparation error")
}

err := SendWebhook(nil, "http://dummy.com", "webhookId", "secretHash")
if err == nil || err.Error() != "request preparation error" {
t.Fatalf("Expected request preparation error, but got: %v", err)
}
})

t.Run("Failed webhook due to response processing errors", func(t *testing.T) {
resetMocks()
processResponse = func(resp *http.Response) (string, []byte, error) {
return "failed", nil, errors.New("response processing error")
}

err := SendWebhook(nil, "http://dummy.com", "webhookId", "secretHash")
if err == nil || err.Error() != "response processing error" {
t.Fatalf("Expected response processing error, but got: %v", err)
}
})

t.Run("Logging on failed webhook delivery", func(t *testing.T) {
resetMocks()
processResponse = func(resp *http.Response) (string, []byte, error) {
return "failed", []byte("error body"), nil
}

webhookLoggerInvoked = false
err := SendWebhook(nil, "http://dummy.com", "webhookId", "secretHash")
if err == nil || err.Error() != "failed" {
t.Fatalf("Expected failed status, but got: %v", err)
}

if !webhookLoggerInvoked {
t.Fatalf("Expected WebhookLogger to be invoked")
}
})
}

func resetMocks() {
marshalJSON = marshalJSONOrig
prepareRequest = prepareRequestOrig
sendRequest = sendRequestOrig
processResponse = processResponseOrig
}

0 comments on commit 4f31a81

Please sign in to comment.