Skip to content

Commit 2e338a5

Browse files
feat: add a method for getting raw responses (#28)
* feat: add a method for getting Raw responses * add a little bit of tests * make Do use Raw * always return the response body * add a test * Update client.go Co-authored-by: Divyansh Verma <divyansh@hasura.io> * fix tests --------- Co-authored-by: Divyansh Verma <divyansh@hasura.io>
1 parent 3095df5 commit 2e338a5

File tree

2 files changed

+208
-16
lines changed

2 files changed

+208
-16
lines changed

client.go

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"context"
66
"encoding/json"
7+
"errors"
78
"fmt"
89
"io"
910
"net/http"
@@ -41,7 +42,35 @@ func NewClient(gqlEndpoint string, opt *ClientOpts) *Client {
4142
return c
4243
}
4344

45+
var (
46+
ErrHTTPRequestRedirect = errors.New("http request redirected")
47+
ErrHTTPRequestFailed = errors.New("http request failed")
48+
)
49+
50+
// Do performs a gql query and returns early if faced with a non-successful http status code.
4451
func (c *Client) Do(ctx context.Context, q Queryable) (*bytes.Buffer, error) {
52+
resp, err := c.Raw(ctx, q)
53+
if err != nil {
54+
return nil, err
55+
}
56+
defer resp.Body.Close()
57+
58+
var respBytes bytes.Buffer
59+
_, err = io.Copy(&respBytes, resp.Body)
60+
61+
switch {
62+
case resp.StatusCode > 299 && resp.StatusCode < 399:
63+
err = fmt.Errorf("%w: %d", ErrHTTPRequestRedirect, resp.StatusCode)
64+
case resp.StatusCode > 399:
65+
err = fmt.Errorf("%w: %d", ErrHTTPRequestFailed, resp.StatusCode)
66+
}
67+
68+
return &respBytes, err
69+
}
70+
71+
// Raw performs a gql query and returns the raw http response and error from the underlying http client.
72+
// Make sure to close the response body.
73+
func (c *Client) Raw(ctx context.Context, q Queryable) (*http.Response, error) {
4574
reqObj := graphqlRequest{
4675
Query: q.Query(),
4776
Variables: q.Variables(),
@@ -62,20 +91,5 @@ func (c *Client) Do(ctx context.Context, q Queryable) (*bytes.Buffer, error) {
6291
req.Header.Add(key, value)
6392
}
6493

65-
resp, err := c.httpClient.Do(req)
66-
if err != nil {
67-
return nil, err
68-
}
69-
defer resp.Body.Close()
70-
71-
switch {
72-
case resp.StatusCode > 299 && resp.StatusCode < 399:
73-
return nil, fmt.Errorf("redirected request with http status code: %d", resp.StatusCode)
74-
case resp.StatusCode > 399:
75-
return nil, fmt.Errorf("error response with http status code: %d", resp.StatusCode)
76-
}
77-
78-
var respBytes bytes.Buffer
79-
_, err = io.Copy(&respBytes, resp.Body)
80-
return &respBytes, err
94+
return c.httpClient.Do(req)
8195
}

client_test.go

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
package eywa_test
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"errors"
7+
"io"
8+
"net/http"
9+
"net/http/httptest"
10+
"testing"
11+
12+
"github.com/imperfect-fourth/eywa"
13+
)
14+
15+
type MockQuerable struct {
16+
QueryStr string `json:"query"`
17+
Vars map[string]interface{} `json:"variables"`
18+
}
19+
20+
func (m *MockQuerable) Query() string {
21+
return m.QueryStr
22+
}
23+
24+
func (m *MockQuerable) Variables() map[string]interface{} {
25+
return m.Vars
26+
}
27+
28+
func TestDoClient(t *testing.T) {
29+
tt := []struct {
30+
name string
31+
server *httptest.Server
32+
expectedErr error
33+
expectedResponse []byte
34+
}{
35+
{
36+
name: "Valid GQL response",
37+
server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
38+
w.WriteHeader(http.StatusOK)
39+
w.Write([]byte(`{"data": {"test": "test"}}`))
40+
})),
41+
expectedResponse: []byte(`{"data": {"test": "test"}}`),
42+
},
43+
{
44+
name: "A 401 response",
45+
server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
46+
w.WriteHeader(http.StatusUnauthorized)
47+
w.Write([]byte(`{"error": {"message": "unauthorized"}}`))
48+
})),
49+
expectedErr: eywa.ErrHTTPRequestFailed,
50+
},
51+
{
52+
name: "A 401 status with no response",
53+
server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
54+
w.WriteHeader(http.StatusUnauthorized)
55+
})),
56+
expectedErr: eywa.ErrHTTPRequestFailed,
57+
},
58+
{
59+
name: "A 403 response",
60+
server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
61+
w.WriteHeader(http.StatusUnauthorized)
62+
w.Write([]byte(`{"error": {"message": "forbidden"}}`))
63+
})),
64+
expectedErr: eywa.ErrHTTPRequestFailed,
65+
},
66+
{
67+
name: "A 503 non-json response",
68+
server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
69+
w.WriteHeader(http.StatusServiceUnavailable)
70+
w.Write([]byte(`Service unavailable`))
71+
})),
72+
expectedErr: eywa.ErrHTTPRequestFailed,
73+
},
74+
{
75+
name: "A 301 Redirect",
76+
server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
77+
w.WriteHeader(http.StatusMovedPermanently)
78+
w.Write([]byte(`Moved Permanently`))
79+
})),
80+
expectedErr: eywa.ErrHTTPRequestRedirect,
81+
},
82+
}
83+
84+
for _, tc := range tt {
85+
t.Run(tc.name, func(t *testing.T) {
86+
gqlClient := eywa.NewClient(tc.server.URL, nil)
87+
resp, err := gqlClient.Do(context.TODO(), &MockQuerable{})
88+
if err != nil && tc.expectedErr == nil {
89+
t.Errorf("Expected no error, got %v", err)
90+
return
91+
}
92+
if err == nil && tc.expectedErr != nil {
93+
t.Errorf("Expected error %v, got nil", tc.expectedErr)
94+
return
95+
}
96+
if err != nil && tc.expectedErr != nil {
97+
if !errors.Is(err, tc.expectedErr) {
98+
t.Errorf("Expected error %v, got %v", tc.expectedErr, err)
99+
return
100+
}
101+
return
102+
}
103+
if !bytes.Equal(tc.expectedResponse, resp.Bytes()) {
104+
t.Errorf("Expected response %s, got %s", string(tc.expectedResponse), resp.String())
105+
return
106+
}
107+
})
108+
}
109+
}
110+
111+
func TestRawClient(t *testing.T) {
112+
tt := []struct {
113+
name string
114+
server *httptest.Server
115+
expectedErr error
116+
expectedResponse []byte
117+
}{
118+
{
119+
name: "Valid GQL response",
120+
server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
121+
w.WriteHeader(http.StatusOK)
122+
w.Write([]byte(`{"data": {"test": "test"}}`))
123+
})),
124+
expectedResponse: []byte(`{"data": {"test": "test"}}`),
125+
},
126+
{
127+
name: "A 401 response",
128+
server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
129+
w.WriteHeader(http.StatusUnauthorized)
130+
w.Write([]byte(`{"error": {"message": "unauthorized"}}`))
131+
})),
132+
expectedResponse: []byte(`{"error": {"message": "unauthorized"}}`),
133+
},
134+
{
135+
name: "A 403 response",
136+
server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
137+
w.WriteHeader(http.StatusUnauthorized)
138+
w.Write([]byte(`{"error": {"message": "forbidden"}}`))
139+
})),
140+
expectedResponse: []byte(`{"error": {"message": "forbidden"}}`),
141+
},
142+
{
143+
name: "A 503 non-json response",
144+
server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
145+
w.WriteHeader(http.StatusServiceUnavailable)
146+
w.Write([]byte(`Service unavailable`))
147+
})),
148+
expectedResponse: []byte(`Service unavailable`),
149+
},
150+
}
151+
152+
for _, tc := range tt {
153+
t.Run(tc.name, func(t *testing.T) {
154+
gqlClient := eywa.NewClient(tc.server.URL, nil)
155+
resp, err := gqlClient.Raw(context.TODO(), &MockQuerable{})
156+
if err != nil && tc.expectedErr == nil {
157+
t.Errorf("Expected no error, got %v", err)
158+
return
159+
}
160+
if err == nil && tc.expectedErr != nil {
161+
t.Errorf("Expected error %v, got nil", tc.expectedErr)
162+
return
163+
}
164+
defer resp.Body.Close()
165+
166+
respBody, err := io.ReadAll(resp.Body)
167+
if err != nil {
168+
t.Errorf("Error reading response body: %v", err)
169+
return
170+
}
171+
172+
if !bytes.Equal(tc.expectedResponse, respBody) {
173+
t.Errorf("Expected response %s, got %s", string(tc.expectedResponse), string(respBody))
174+
return
175+
}
176+
})
177+
}
178+
}

0 commit comments

Comments
 (0)