@@ -3,6 +3,7 @@ package e2eapi
33import (
44 "context"
55 "errors"
6+ "fmt"
67 "io"
78 "net/http"
89 "net/http/httptest"
@@ -21,42 +22,34 @@ func TestInstance(t *testing.T) {
2122 ctx , cancel := context .WithTimeout (context .Background (), time .Second * 4 )
2223 defer cancel ()
2324
24- globalCfg := e2e .Must (e2e .Config ())
25- inst , err := New (globalCfg )
26- if err != nil {
27- t .Fatalf ("exp nil err; got %v" , err )
28- }
29- defer inst .Close ()
30-
31- {
32- email := "e2etesthooks_" + uuid .Must (uuid .NewV4 ()).String () + "@localhost"
33- req := & api.SignupParams {
34- Email : email ,
35- Password : "password" ,
36- }
37- res := new (models.User )
38- err := Do (ctx , http .MethodPost , inst .APIServer .URL + "/signup" , req , res )
39- if err != nil {
40- t .Fatalf ("exp nil err; got %v" , err )
41- }
42- require .Equal (t , email , res .Email .String ())
43- }
44- }
25+ t .Run ("New" , func (t * testing.T ) {
26+ t .Run ("Success" , func (t * testing.T ) {
27+ globalCfg := e2e .Must (e2e .Config ())
28+ inst , err := New (globalCfg )
29+ require .NoError (t , err )
30+ defer inst .Close ()
31+
32+ email := "e2etesthooks_" + uuid .Must (uuid .NewV4 ()).String () + "@localhost"
33+ req := & api.SignupParams {
34+ Email : email ,
35+ Password : "password" ,
36+ }
37+ res := new (models.User )
38+ err = Do (ctx , http .MethodPost , inst .APIServer .URL + "/signup" , req , res )
39+ require .NoError (t , err )
40+ require .Equal (t , email , res .Email .String ())
41+ })
4542
46- func TestNew (t * testing.T ) {
47- {
48- globalCfg := e2e .Must (e2e .Config ())
49- globalCfg .DB .Driver = ""
50- globalCfg .DB .URL = "invalid"
43+ t .Run ("Failure" , func (t * testing.T ) {
44+ globalCfg := e2e .Must (e2e .Config ())
45+ globalCfg .DB .Driver = ""
46+ globalCfg .DB .URL = "invalid"
5147
52- inst , err := New (globalCfg )
53- if err == nil {
54- t .Fatal ("exp non-nil err" )
55- }
56- if inst != nil {
57- t .Fatal ("exp nil *Instance" )
58- }
59- }
48+ inst , err := New (globalCfg )
49+ require .Error (t , err )
50+ require .Nil (t , inst )
51+ })
52+ })
6053}
6154
6255func TestDo (t * testing.T ) {
@@ -65,63 +58,57 @@ func TestDo(t *testing.T) {
6558
6659 globalCfg := e2e .Must (e2e .Config ())
6760 inst , err := New (globalCfg )
68- if err != nil {
69- t .Fatalf ("exp nil err; got %v" , err )
70- }
61+ require .NoError (t , err )
7162 defer inst .Close ()
7263
73- {
64+ // Covers calls to Do with a `req` param type which can't marshaled
65+ t .Run ("InvalidRequestType" , func (t * testing.T ) {
7466 req := make (chan string )
7567 err := Do (ctx , http .MethodPost , "http://localhost" , & req , nil )
76- if err == nil {
77- t .Fatal ("exp non-nil err" )
78- }
68+ require .Error (t , err )
7969 require .ErrorContains (t , err , "json: unsupported type: chan string" )
80- }
70+ })
8171
82- {
83- res := make (chan string )
84- err := Do (ctx , http .MethodGet , inst .APIServer .URL + "/user" , nil , & res )
85- if err == nil {
86- t .Fatal ("exp non-nil err" )
87- }
88- require .ErrorContains (t , err , "401: This endpoint requires a Bearer token" )
89- }
90-
91- {
72+ // Covers calls to Do with a `res` param type which can't marshaled
73+ t .Run ("InvalidResponseType" , func (t * testing.T ) {
9274 res := make (chan string )
9375 err := Do (ctx , http .MethodGet , inst .APIServer .URL + "/settings" , nil , & res )
94- if err == nil {
95- t .Fatal ("exp non-nil err" )
96- }
76+ require .Error (t , err )
9777 require .ErrorContains (t , err , "json: cannot unmarshal object into Go value of type chan string" )
98- }
78+ })
79+
80+ // Covers status code >= 400 error handling switch statement
81+ t .Run ("api.HTTPErrorResponse_to_apierrors.HTTPError" , func (t * testing.T ) {
82+ res := make (chan string )
83+ err := Do (ctx , http .MethodGet , inst .APIServer .URL + "/user" , nil , & res )
84+ require .Error (t , err )
85+ require .ErrorContains (t , err , "401: This endpoint requires a Bearer token" )
86+ })
9987
100- {
88+ // Covers http.NewRequestWithContext
89+ t .Run ("InvalidHTTPMethod" , func (t * testing.T ) {
10190 err := Do (ctx , "\x01 " , "http://localhost" , nil , nil )
102- if err == nil {
103- t .Fatal ("exp non-nil err" )
104- }
91+ require .Error (t , err )
10592 require .ErrorContains (t , err , "net/http: invalid method" )
106- }
93+ })
10794
108- {
95+ // Covers status code >= 400 error handling switch statement json.Unmarshal
96+ // by hitting the default error handler that returns html
97+ t .Run ("InvalidResponse" , func (t * testing.T ) {
10998 err := Do (ctx , http .MethodGet , inst .APIServer .URL + "/404" , nil , nil )
110- if err == nil {
111- t .Fatal ("exp non-nil err" )
112- }
99+ require .Error (t , err )
113100 require .ErrorContains (t , err , "invalid character" )
114- }
101+ })
115102
116- {
103+ // Covers defaultClient.Do failure
104+ t .Run ("InvalidURL" , func (t * testing.T ) {
117105 err := Do (ctx , http .MethodPost , "invalid" , nil , nil )
118- if err == nil {
119- t .Fatal ("exp non-nil err" )
120- }
106+ require .Error (t , err )
121107 require .ErrorContains (t , err , "unsupported protocol" )
122- }
108+ })
123109
124- func () {
110+ // Covers http.StatusNoContent handling
111+ t .Run ("InvalidRequestType" , func (t * testing.T ) {
125112 hr := http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
126113 w .WriteHeader (http .StatusNoContent )
127114 })
@@ -130,46 +117,71 @@ func TestDo(t *testing.T) {
130117 defer ts .Close ()
131118
132119 err := Do (ctx , http .MethodPost , ts .URL , nil , nil )
133- if err != nil {
134- t .Fatalf ("exp nil err; got %v" , err )
135- }
136- }()
137-
138- for _ , statusCode := range []int {http .StatusBadRequest , http .StatusOK } {
139- func () {
140- sentinel := errors .New ("sentinel" )
141- rtFn := roundTripperFunc (func (req * http.Request ) (* http.Response , error ) {
142- res , err := http .DefaultClient .Do (req )
143- if err != nil {
144- return nil , err
145- }
146- res .Body = io .NopCloser (iotest .ErrReader (sentinel ))
147- return res , nil
148- })
149-
150- prev := defaultClient
151- defer func () {
152- defaultClient = prev
153- }()
154- defaultClient = new (http.Client )
155- defaultClient .Transport = rtFn
120+ require .NoError (t , err )
121+ })
122+
123+ // Covers IO errors
124+ t .Run ("IOError" , func (t * testing.T ) {
125+
126+ for _ , statusCode := range []int {http .StatusBadRequest , http .StatusOK } {
127+
128+ // Covers IO errors for the sc >= 400 and default status code
129+ // handling in the switch statement within do.
130+ testName := fmt .Sprintf ("Status=%v" , http .StatusText (statusCode ))
131+ t .Run (testName , func (t * testing.T ) {
132+
133+ // We assign a sentinel error to ensure propagation.
134+ sentinel := errors .New ("sentinel" )
135+
136+ // This implementation of the http.RoundTripper is a way to
137+ // cover the io.ReadAll(io.LimitReader(...)) lines in the switch
138+ // statements inside do().
139+ rtFn := roundTripperFunc (func (req * http.Request ) (* http.Response , error ) {
140+
141+ // Call the default http.RoundTripper implementation provided
142+ // by the http.Default client to build a valid http.Response.
143+ res , err := http .DefaultClient .Do (req )
144+ if err != nil {
145+ return nil , err
146+ }
147+
148+ // Wrap the res.Body in an io.ErrReader using our sentinel
149+ // error. This causes the first call to read the response
150+ // body to return our sentinel error.
151+ res .Body = io .NopCloser (iotest .ErrReader (sentinel ))
152+ return res , nil
153+ })
154+
155+ // We need to swap the defaultClient with a new client which has
156+ // the (*Client).Transport set to our http.RoundTripper above.
157+ prev := defaultClient
158+ defer func () {
159+ defaultClient = prev
160+ }()
161+ defaultClient = new (http.Client )
162+ defaultClient .Transport = rtFn
163+
164+ hr := http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
165+ w .WriteHeader (statusCode )
166+ })
167+
168+ ts := httptest .NewServer (hr )
169+ defer ts .Close ()
170+
171+ // We send the request and expect back our sentinel error.
172+ err := Do (ctx , http .MethodPost , ts .URL , nil , nil )
173+ require .Error (t , err )
174+ require .Equal (t , sentinel , err )
156175
157- hr := http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
158- w .WriteHeader (statusCode )
159176 })
160-
161- ts := httptest .NewServer (hr )
162- defer ts .Close ()
163-
164- err := Do (ctx , http .MethodPost , ts .URL , nil , nil )
165- require .Error (t , err )
166- require .Equal (t , sentinel , err )
167- }()
168- }
177+ }
178+ })
169179}
170180
181+ // roundTripperFunc is like http.HandlerFunc for a http.RoundTripper
171182type roundTripperFunc func (* http.Request ) (* http.Response , error )
172183
184+ // RoundTrip implements http.RoundTripper by calling itself.
173185func (f roundTripperFunc ) RoundTrip (req * http.Request ) (* http.Response , error ) {
174186 return f (req )
175187}
0 commit comments