Skip to content

Commit 5c07bc9

Browse files
authored
Merge pull request #109 from deploymenttheory/dev
+ documentation for cookiejar
2 parents 4485a95 + aaca1c1 commit 5c07bc9

File tree

6 files changed

+148
-3
lines changed

6 files changed

+148
-3
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ This client leverages API-specific SDKs to provide a comprehensive and consisten
1818
- **API Handler Interface**: Provides a flexible and extensible way to interact with different APIs, including encoding and decoding requests and responses, managing authentication endpoints, and handling API-specific logic.
1919
- **Configuration via JSON or Environment Variables**: The Go API HTTP Client supports configuration via JSON files or environment variables, providing flexibility in defining authentication credentials, API endpoints, logging settings, and other parameters.
2020

21+
- **Cookie Jar Support**: Incorporates an optional cookie jar to manage cookies effortlessly across requests, enhancing session management and statefulness with APIs that require cookie-based authentication or tracking. This feature allows for automatic storage and sending of cookies with subsequent requests, mirroring browser-like interaction with web services. It can be enabled or disabled based on configuration, providing flexibility in how stateful interactions are handled with the target API.
22+
2123
## API Handler
2224

2325
The `APIHandler` interface abstracts the functionality needed to interact with various APIs, making the HTTP client adaptable to different API implementations. It includes methods for constructing resource and authentication endpoints, marshaling requests, handling responses, and managing API-specific headers.
@@ -86,6 +88,10 @@ Example configuration file (clientconfig.json):
8688
"LogOutputFormat": "console", // "console" / "json"
8789
"LogConsoleSeparator": " ", // " " / "\t" / "," / etc.
8890
"HideSensitiveData": true, // true / false
91+
"EnableDynamicRateLimiting": true, // true / false
92+
"MaxRetryAttempts": 5,
93+
"MaxConcurrentRequests": 3,
94+
"EnableCookieJar": true // true / false
8995
}
9096
}
9197
```

httpclient/httpclient_cookies.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// httpclient_cookies.go
12
package httpclient
23

34
import (
@@ -11,7 +12,7 @@ func RedactSensitiveCookies(cookies []*http.Cookie) []*http.Cookie {
1112
// Define sensitive cookie names that should be redacted.
1213
sensitiveCookieNames := map[string]bool{
1314
"SessionID": true, // Example sensitive cookie name
14-
// Add more sensitive cookie names as needed.
15+
// More sensitive cookie names will be added as needed.
1516
}
1617

1718
// Iterate over the cookies and redact sensitive ones.
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// httpclient_cookies_test.go
2+
package httpclient
3+
4+
import (
5+
"net/http"
6+
"testing"
7+
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
// TestRedactSensitiveCookies tests the RedactSensitiveCookies function to ensure it correctly redacts sensitive cookies.
12+
func TestRedactSensitiveCookies(t *testing.T) {
13+
cookies := []*http.Cookie{
14+
{Name: "SessionID", Value: "sensitive-value-1"},
15+
{Name: "NonSensitiveCookie", Value: "non-sensitive-value"},
16+
{Name: "AnotherSensitiveCookie", Value: "sensitive-value-2"},
17+
}
18+
19+
redactedCookies := RedactSensitiveCookies(cookies)
20+
21+
// Define expected outcomes for each cookie.
22+
expectedValues := map[string]string{
23+
"SessionID": "REDACTED",
24+
"NonSensitiveCookie": "non-sensitive-value",
25+
"AnotherSensitiveCookie": "sensitive-value-2", // Assuming this is not in the sensitive list.
26+
}
27+
28+
for _, cookie := range redactedCookies {
29+
assert.Equal(t, expectedValues[cookie.Name], cookie.Value, "Cookie value should match expected redaction outcome")
30+
}
31+
}
32+
33+
// TestCookiesFromHeader tests the CookiesFromHeader function to ensure it can correctly parse cookies from HTTP headers.
34+
func TestCookiesFromHeader(t *testing.T) {
35+
header := http.Header{
36+
"Set-Cookie": []string{
37+
"SessionID=sensitive-value; Path=/; HttpOnly",
38+
"NonSensitiveCookie=non-sensitive-value; Path=/",
39+
},
40+
}
41+
42+
cookies := CookiesFromHeader(header)
43+
44+
// Define expected outcomes for each cookie.
45+
expectedCookies := []*http.Cookie{
46+
{Name: "SessionID", Value: "sensitive-value"},
47+
{Name: "NonSensitiveCookie", Value: "non-sensitive-value"},
48+
}
49+
50+
assert.Equal(t, len(expectedCookies), len(cookies), "Number of parsed cookies should match expected")
51+
52+
for i, expectedCookie := range expectedCookies {
53+
assert.Equal(t, expectedCookie.Name, cookies[i].Name, "Cookie names should match")
54+
assert.Equal(t, expectedCookie.Value, cookies[i].Value, "Cookie values should match")
55+
}
56+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// httpclient_error_response_test.go
2+
// This package provides utility functions and structures for handling and categorizing HTTP error responses.
3+
package httpclient
4+
5+
import (
6+
"bytes"
7+
"io"
8+
"net/http"
9+
"testing"
10+
11+
"github.com/stretchr/testify/assert"
12+
)
13+
14+
// TestHandleAPIErrorResponse tests the handleAPIErrorResponse function with different types of error responses.
15+
func TestHandleAPIErrorResponse(t *testing.T) {
16+
tests := []struct {
17+
name string
18+
response *http.Response
19+
expectedAPIErr *APIError
20+
}{
21+
{
22+
name: "Structured JSON Error",
23+
response: &http.Response{
24+
StatusCode: 400,
25+
Header: http.Header{"Content-Type": []string{"application/json"}},
26+
Body: io.NopCloser(bytes.NewBufferString(`{"error":{"code":"INVALID","message":"Invalid request"}}`)),
27+
},
28+
expectedAPIErr: &APIError{
29+
StatusCode: 400,
30+
Type: "APIError",
31+
Message: "Invalid request",
32+
Detail: "",
33+
Errors: nil,
34+
Raw: `{"error":{"code":"INVALID","message":"Invalid request"}}`,
35+
},
36+
},
37+
{
38+
name: "Generic JSON Error",
39+
response: &http.Response{
40+
StatusCode: 500,
41+
Header: http.Header{"Content-Type": []string{"application/json"}},
42+
Body: io.NopCloser(bytes.NewBufferString(`{"message":"Internal server error","detail":"Error details"}`)),
43+
},
44+
expectedAPIErr: &APIError{
45+
StatusCode: 500,
46+
Type: "APIError",
47+
Message: "Internal server error",
48+
Detail: "Error details",
49+
Errors: nil,
50+
Raw: `{"message":"Internal server error","detail":"Error details"}`,
51+
},
52+
},
53+
{
54+
name: "Non-JSON Error",
55+
response: &http.Response{
56+
StatusCode: 404,
57+
Header: http.Header{"Content-Type": []string{"text/html"}},
58+
Body: io.NopCloser(bytes.NewBufferString("<html>Not Found</html>")),
59+
},
60+
expectedAPIErr: &APIError{
61+
StatusCode: 404,
62+
Type: "APIError",
63+
Message: "An error occurred",
64+
Detail: "",
65+
Errors: nil,
66+
Raw: "<html>Not Found</html>",
67+
},
68+
},
69+
}
70+
71+
for _, tt := range tests {
72+
t.Run(tt.name, func(t *testing.T) {
73+
mockLog := NewMockLogger()
74+
apiError := handleAPIErrorResponse(tt.response, mockLog)
75+
76+
assert.Equal(t, tt.expectedAPIErr.StatusCode, apiError.StatusCode)
77+
assert.Equal(t, tt.expectedAPIErr.Message, apiError.Message)
78+
assert.Equal(t, tt.expectedAPIErr.Detail, apiError.Detail)
79+
assert.Equal(t, tt.expectedAPIErr.Raw, apiError.Raw)
80+
})
81+
}
82+
}

httpclient/httpclient_version.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ package httpclient
44
import "fmt"
55

66
const (
7-
SDKVersion = "0.0.8"
7+
SDKVersion = "0.0.90"
88
UserAgentBase = "go-api-http-client"
99
)
1010

version/version.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ package version
55
var AppName = "go-api-http-client"
66

77
// Version holds the current version of the application
8-
var Version = "0.0.65"
8+
var Version = "0.0.91"
99

1010
// GetAppName returns the name of the application
1111
func GetAppName() string {

0 commit comments

Comments
 (0)