Skip to content

Commit

Permalink
Update test for testify suite
Browse files Browse the repository at this point in the history
  • Loading branch information
pzentenoe committed Jul 3, 2024
1 parent 6e31001 commit 3cf8cb4
Show file tree
Hide file tree
Showing 9 changed files with 525 additions and 168 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,5 +158,5 @@ This project is released under the MIT License. See the [LICENSE](LICENSE) file
## Changelog
For a detailed changelog, refer to [CHANGELOG.md](CHANGELOG.md).

## Autor
## Author
- **Pablo Zenteno** - _Full Stack Developer_ - [pzentenoe](https://github.com/pzentenoe)
21 changes: 12 additions & 9 deletions http_client_call.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ package client

import (
"context"
"encoding/json"
"errors"
"io"
"fmt"
"net/http"
"net/url"
)
Expand All @@ -18,12 +17,12 @@ const (

// HTTPClientDoer is an interface for executing an HTTP request.
type HTTPClientDoer interface {
Do() (*http.Response, error)
Do(*http.Request) (*http.Response, error)
}

// HTTPClientCall encapsulates the configuration and execution of an HTTP request.
type HTTPClientCall struct {
client *http.Client
client HTTPClientDoer
method string
host string
path string
Expand All @@ -35,7 +34,7 @@ type HTTPClientCall struct {
}

// NewHTTPClientCall creates a new HTTPClientCall with the specified host and HTTP client.
func NewHTTPClientCall(host string, client *http.Client) *HTTPClientCall {
func NewHTTPClientCall(host string, client HTTPClientDoer) *HTTPClientCall {
if client == nil {
panic("You must create client")
}
Expand Down Expand Up @@ -137,14 +136,18 @@ func (r *HTTPClientCall) DoWithUnmarshal(ctx context.Context, responseBody any)
defer func() {
_ = resp.Body.Close()
}()
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err

contentType := resp.Header.Get("Content-Type")
decoder := selectDecoder(contentType)
if decoder == nil {
return nil, fmt.Errorf("unsupported content type: %s", contentType)
}
err = json.Unmarshal(data, &responseBody)

err = decoder.Decode(resp.Body, responseBody)
if err != nil {
return nil, err
}

httpClientCallResponse := &HTTPClientCallResponse{
StatusCode: resp.StatusCode,
}
Expand Down
50 changes: 50 additions & 0 deletions http_client_call_decoder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package client

import (
"encoding/json"
"fmt"
"io"
"strings"
)

// ResponseDecoder is an interface for decoding an HTTP response body.
type ResponseDecoder interface {
Decode(io.Reader, any) error
}

// JSONResponseDecoder decodes JSON-encoded HTTP response bodies.
type JSONResponseDecoder struct{}

// Decode decodes a JSON-encoded response body into the provided interface.
func (d *JSONResponseDecoder) Decode(r io.Reader, v any) error {
return json.NewDecoder(r).Decode(v)
}

// StringResponseDecoder decodes plain text or HTML-encoded HTTP response bodies.
type StringResponseDecoder struct{}

// Decode decodes a plain text or HTML-encoded response body into the provided string.
func (d *StringResponseDecoder) Decode(r io.Reader, v any) error {
if str, ok := v.(*string); ok {
data, err := io.ReadAll(r)
if err != nil {
return err
}
*str = string(data)
return nil
}
return fmt.Errorf("StringResponseDecoder: unsupported type %T", v)
}

// selectDecoder selects the appropriate ResponseDecoder based on the Content-Type of the response.
func selectDecoder(contentType string) ResponseDecoder {
switch {
case strings.Contains(contentType, MIMEApplicationJSON):
return &JSONResponseDecoder{}
case strings.Contains(contentType, MIMETextPlain), strings.Contains(contentType, MIMETextHTML):
return &StringResponseDecoder{}
// Add more cases as needed
default:
return nil
}
}
104 changes: 104 additions & 0 deletions http_client_call_decoder_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package client

import (
"bytes"
"io"
"testing"

"github.com/stretchr/testify/suite"
)

type HTTPClientCallDecoderSuite struct {
suite.Suite
}

func (suite *HTTPClientCallDecoderSuite) TestJSONResponseDecoder_Decode() {
suite.Run("decodes JSON response successfully", func() {
decoder := &JSONResponseDecoder{}
body := `{"key":"value"}`
r := io.NopCloser(bytes.NewBufferString(body))
var result map[string]string

err := decoder.Decode(r, &result)
suite.NoError(err)
suite.Equal("value", result["key"])
})

suite.Run("returns error for invalid JSON", func() {
decoder := &JSONResponseDecoder{}
body := `{"key":"value"`
r := io.NopCloser(bytes.NewBufferString(body))
var result map[string]string

err := decoder.Decode(r, &result)
suite.Error(err)
})
}

func (suite *HTTPClientCallDecoderSuite) TestStringResponseDecoder_Decode() {
suite.Run("decodes plain text response successfully", func() {
decoder := &StringResponseDecoder{}
body := "Hello, world!"
r := io.NopCloser(bytes.NewBufferString(body))
var result string

err := decoder.Decode(r, &result)
suite.NoError(err)
suite.Equal("Hello, world!", result)
})

suite.Run("decodes HTML response successfully", func() {
decoder := &StringResponseDecoder{}
body := "<html><body>Hello, world!</body></html>"
r := io.NopCloser(bytes.NewBufferString(body))
var result string

err := decoder.Decode(r, &result)
suite.NoError(err)
suite.Equal("<html><body>Hello, world!</body></html>", result)
})

suite.Run("returns error for unsupported type", func() {
decoder := &StringResponseDecoder{}
body := "Hello, world!"
r := io.NopCloser(bytes.NewBufferString(body))
var result int

err := decoder.Decode(r, &result)
suite.Error(err)
suite.EqualError(err, "StringResponseDecoder: unsupported type *int")
})
}

func (suite *HTTPClientCallDecoderSuite) TestSelectDecoder() {
suite.Run("selects JSONResponseDecoder for application/json", func() {
contentType := "application/json"
decoder := selectDecoder(contentType)
_, ok := decoder.(*JSONResponseDecoder)
suite.True(ok)
})

suite.Run("selects StringResponseDecoder for text/plain", func() {
contentType := "text/plain"
decoder := selectDecoder(contentType)
_, ok := decoder.(*StringResponseDecoder)
suite.True(ok)
})

suite.Run("selects StringResponseDecoder for text/html", func() {
contentType := "text/html"
decoder := selectDecoder(contentType)
_, ok := decoder.(*StringResponseDecoder)
suite.True(ok)
})

suite.Run("returns nil for unsupported content type", func() {
contentType := "application/xml"
decoder := selectDecoder(contentType)
suite.Nil(decoder)
})
}

func TestHTTPClientCallDecoderSuite(t *testing.T) {
suite.Run(t, new(HTTPClientCallDecoderSuite))
}
11 changes: 11 additions & 0 deletions headers.go → http_client_call_headers.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package client

import "net/http"

// Headers
const (
HeaderAccept = "Accept"
Expand Down Expand Up @@ -50,3 +52,12 @@ const (
HeaderXCSRFToken = "X-CSRF-Token"
HeaderReferrerPolicy = "Referrer-Policy"
)

// setHeaders sets the headers for the HTTP request.
func (r *HTTPClientCall) setHeaders(req *http.Request) {
for key, values := range r.headers {
for _, value := range values {
req.Header.Add(key, value)
}
}
}
59 changes: 59 additions & 0 deletions http_client_call_headers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package client

import (
"net/http"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)

type HTTPClientCallHeadersSuite struct {
suite.Suite
call *HTTPClientCall
req *http.Request
}

func (suite *HTTPClientCallHeadersSuite) SetupTest() {
client := &http.Client{}
suite.call = NewHTTPClientCall("http://example.com", client)
suite.req, _ = http.NewRequest("GET", "http://example.com", nil)
}

func (suite *HTTPClientCallHeadersSuite) TestSetHeaders() {
suite.Run("sets single header correctly", func() {
headers := http.Header{
HeaderContentType: []string{"application/json"},
}
suite.call.Headers(headers)
suite.call.setHeaders(suite.req)

assert.Equal(suite.T(), "application/json", suite.req.Header.Get(HeaderContentType))
})

suite.Run("sets multiple headers correctly", func() {
headers := http.Header{
HeaderContentType: []string{"application/json"},
HeaderAccept: []string{"application/json"},
}
suite.call.Headers(headers)
suite.call.setHeaders(suite.req)

assert.Equal(suite.T(), "application/json", suite.req.Header.Get(HeaderContentType))
assert.Equal(suite.T(), "application/json", suite.req.Header.Get(HeaderAccept))
})

suite.Run("sets multiple values for a single header correctly", func() {
headers := http.Header{
HeaderAcceptEncoding: []string{"gzip", "deflate"},
}
suite.call.Headers(headers)
suite.call.setHeaders(suite.req)

assert.ElementsMatch(suite.T(), []string{"gzip", "deflate"}, suite.req.Header[HeaderAcceptEncoding])
})
}

func TestHTTPClientCallHeadersSuite(t *testing.T) {
suite.Run(t, new(HTTPClientCallHeadersSuite))
}
Loading

0 comments on commit 3cf8cb4

Please sign in to comment.