Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interfaced HTTP Operations (stage 1) to facilitate unit testing #258

Merged
merged 2 commits into from
Jul 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,14 @@ go 1.22.4
require (
github.com/antchfx/xmlquery v1.4.1
github.com/google/uuid v1.6.0
github.com/stretchr/testify v1.9.0
go.uber.org/zap v1.27.0
golang.org/x/net v0.26.0
)

require (
github.com/antchfx/xpath v1.3.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/testify v1.9.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/text v0.16.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,5 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
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/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
29 changes: 21 additions & 8 deletions httpclient/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,37 @@ package httpclient

import (
"fmt"
"io"
"net/http"
"net/url"
"time"

"github.com/deploymenttheory/go-api-http-client/concurrency"
"go.uber.org/zap"
)

const ()

// TODO all struct comments
type httpExecutor interface {
// Inherited
CloseIdleConnections()
Do(req *http.Request) (*http.Response, error)
Get(url string) (resp *http.Response, err error)
Head(url string) (resp *http.Response, err error)
Post(url string, contentType string, body io.Reader) (resp *http.Response, err error)
PostForm(url string, data url.Values) (resp *http.Response, err error)

// Additional
SetCookieJar(jar http.CookieJar)
SetCookies(url *url.URL, cookies []*http.Cookie)
SetCustomTimeout(time.Duration)
Cookies(*url.URL) []*http.Cookie
SetRedirectPolicy(*func(req *http.Request, via []*http.Request) error)
}

// Master struct/object
type Client struct {
config *ClientConfig
Integration *APIIntegration
http *http.Client
http httpExecutor
Sugar *zap.SugaredLogger
Concurrency *concurrency.ConcurrencyHandler
}
Expand Down Expand Up @@ -104,12 +119,10 @@ func (c *ClientConfig) Build() (*Client, error) {

c.Sugar.Debug("configuration valid")

httpClient := &http.Client{
Timeout: c.CustomTimeout,
}
httpClient := &prodClient{}

if c.CustomRedirectPolicy != nil {
httpClient.CheckRedirect = *c.CustomRedirectPolicy
httpClient.SetRedirectPolicy(c.CustomRedirectPolicy)
}

// TODO refactor concurrency
Expand Down
6 changes: 3 additions & 3 deletions httpclient/cookies.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,20 @@ func (c *Client) loadCustomCookies() error {
return err
}

c.http.Jar = cookieJar
c.http.SetCookieJar(cookieJar)

cookieUrl, err := url.Parse((*c.Integration).GetFQDN())
c.Sugar.Debug("cookie URL set globally to: %s", cookieUrl)
if err != nil {
return err
}

c.http.Jar.SetCookies(cookieUrl, c.config.CustomCookies)
c.http.SetCookies(cookieUrl, c.config.CustomCookies)

if c.config.HideSensitiveData {
c.Sugar.Debug("[REDACTED] cookies set successfully")
} else {
c.Sugar.Debug("custom cookies set: %v", c.http.Jar.Cookies(cookieUrl))
c.Sugar.Debug("custom cookies set: %v", c.http.Cookies(cookieUrl))
}

return nil
Expand Down
31 changes: 31 additions & 0 deletions httpclient/http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package httpclient

import (
"net/http"
"net/url"
"time"
)

type prodClient struct {
*http.Client
}

func (c *prodClient) SetCookieJar(jar http.CookieJar) {
c.Jar = jar
}

func (c *prodClient) SetCookies(url *url.URL, cookies []*http.Cookie) {
c.Jar.SetCookies(url, cookies)
}

func (c *prodClient) SetCustomTimeout(timeout time.Duration) {
c.Timeout = timeout
}

func (c *prodClient) Cookies(url *url.URL) []*http.Cookie {
return c.Jar.Cookies(url)
}

func (c *prodClient) SetRedirectPolicy(policy *func(req *http.Request, via []*http.Request) error) {
c.CheckRedirect = *policy
}
4 changes: 2 additions & 2 deletions httpclient/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import (
// - endpoint: The target API endpoint for the request. This should be a relative path that will be appended to the base URL
// configured for the HTTP client.
// - body: The payload for the request, which will be serialized into the request body. The serialization format (e.g., JSON, XML)
// is determined by the content-type header and the specific implementation of the API handler used by the client.
// is determined by the content-type header and the specific implementation of the API handler used by the client.
// - out: A pointer to an output variable where the response will be deserialized. The function expects this to be a pointer to
// a struct that matches the expected response schema.
//
Expand Down Expand Up @@ -238,7 +238,7 @@ func (c *Client) request(ctx context.Context, method, endpoint string, body inte
if c.config.EnableConcurrencyManagement {
_, requestID, err := c.Concurrency.AcquireConcurrencyPermit(ctx)
if err != nil {
return nil, fmt.Errorf("Failed to acquire concurrency permit: %v", err)
return nil, fmt.Errorf("failed to acquire concurrency permit: %v", err)

}

Expand Down
48 changes: 0 additions & 48 deletions httpclient/utility.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@
package httpclient

import (
"errors"
"fmt"
"net/http"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -38,52 +36,6 @@ func validateFilePath(path string) (string, error) {

}

// validateClientID checks if a client ID is a valid UUID.
func validateValidClientID(clientID string) error {
uuidRegex := `^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$`
if regexp.MustCompile(uuidRegex).MatchString(clientID) {
return nil
}
return errors.New("clientID failed regex check")
}

func validateClientSecret(clientSecret string) error {
if len(clientSecret) < 16 {
return errors.New("client secret must be at least 16 characters long")
}

if matched, _ := regexp.MatchString(`[a-z]`, clientSecret); !matched {
return errors.New("client secret must contain at least one lowercase letter")
}

if matched, _ := regexp.MatchString(`[A-Z]`, clientSecret); !matched {
return errors.New("client secret must contain at least one uppercase letter")
}

if matched, _ := regexp.MatchString(`\d`, clientSecret); !matched {
return errors.New("client secret must contain at least one digit")
}

return nil
}

// validateUsername checks if a username meets the minimum requirements.
func validateUsername(username string) error {
usernameRegex := `^[a-zA-Z0-9!@#$%^&*()_\-\+=\[\]{\}\\|;:'",<.>/?]+$`
if !regexp.MustCompile(usernameRegex).MatchString(username) {
return errors.New("username failed regex test")
}
return nil
}

// validatePassword checks if a password meets the minimum requirements.
func validatePassword(password string) error {
if len(password) < 8 {
return errors.New("password not long enough")
}
return nil
}

// getEnvAsString reads an environment variable as a string, with a fallback default value.
func getEnvAsString(name string, defaultVal string) string {
if value, exists := os.LookupEnv(name); exists {
Expand Down
1 change: 1 addition & 0 deletions mock/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package mock
82 changes: 0 additions & 82 deletions response/t_parse_test.go

This file was deleted.

46 changes: 0 additions & 46 deletions response/t_success_test.go

This file was deleted.

Loading