1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package transport
16
17 import (
18 "bytes"
19 "errors"
20 "io"
21 "net/http"
22 "net/url"
23 "testing"
24
25 "github.com/google/go-cmp/cmp"
26 )
27
28 func TestTemporary(t *testing.T) {
29 tests := []struct {
30 error *Error
31 retry bool
32 }{{
33 error: &Error{},
34 retry: false,
35 }, {
36 error: &Error{
37 Errors: []Diagnostic{{
38 Code: BlobUploadInvalidErrorCode,
39 }},
40 },
41 retry: true,
42 }, {
43 error: &Error{
44 Errors: []Diagnostic{{
45 Code: BlobUploadInvalidErrorCode,
46 }, {
47 Code: DeniedErrorCode,
48 }},
49 },
50 retry: false,
51 }, {
52 error: &Error{
53 Errors: []Diagnostic{{
54 Code: TooManyRequestsErrorCode,
55 }},
56 },
57 retry: true,
58 }, {
59 error: &Error{
60 Errors: []Diagnostic{{
61 Code: UnavailableErrorCode,
62 }},
63 },
64 retry: true,
65 }, {
66 error: &Error{
67 StatusCode: http.StatusInternalServerError,
68 },
69 retry: true,
70 }}
71
72 for _, test := range tests {
73 retry := test.error.Temporary()
74
75 if test.retry != retry {
76 t.Errorf("Temporary(%s) = %t, wanted %t", test.error, retry, test.retry)
77 }
78 }
79 }
80
81 func TestCheckErrorNil(t *testing.T) {
82 tests := []int{
83 http.StatusOK,
84 http.StatusAccepted,
85 http.StatusCreated,
86 http.StatusMovedPermanently,
87 http.StatusInternalServerError,
88 }
89
90 for _, code := range tests {
91 resp := &http.Response{StatusCode: code}
92
93 if err := CheckError(resp, code); err != nil {
94 t.Errorf("CheckError(%d) = %v", code, err)
95 }
96 }
97 }
98
99 func TestCheckErrorNotError(t *testing.T) {
100 tests := []struct {
101 code int
102 body string
103 msg string
104 request *http.Request
105 }{{
106 code: http.StatusBadRequest,
107 body: "",
108 msg: "unexpected status code 400 Bad Request",
109 }, {
110 code: http.StatusUnauthorized,
111
112 body: `{"details":"incorrect username or password"}`,
113 msg: `unexpected status code 401 Unauthorized: {"details":"incorrect username or password"}`,
114 }, {
115 code: http.StatusUnauthorized,
116 body: "Not JSON",
117 msg: "GET https://example.com/somepath?access_token=REDACTED&scope=foo&service=bar: unexpected status code 401 Unauthorized: Not JSON",
118 request: &http.Request{
119 Method: http.MethodGet,
120 URL: &url.URL{
121 Scheme: "https",
122 Host: "example.com",
123 Path: "somepath",
124 RawQuery: url.Values{
125 "scope": []string{"foo"},
126 "service": []string{"bar"},
127 "access_token": []string{"hunter2"},
128 }.Encode(),
129 },
130 },
131 }, {
132 code: http.StatusUnauthorized,
133 body: "",
134 msg: "HEAD https://example.com/somepath: unexpected status code 401 Unauthorized (HEAD responses have no body, use GET for details)",
135 request: &http.Request{
136 Method: http.MethodHead,
137 URL: &url.URL{
138 Scheme: "https",
139 Host: "example.com",
140 Path: "somepath",
141 },
142 },
143 }}
144
145 for _, test := range tests {
146 resp := &http.Response{
147 StatusCode: test.code,
148 Body: io.NopCloser(bytes.NewBufferString(test.body)),
149 Request: test.request,
150 }
151
152 err := CheckError(resp, http.StatusOK)
153 if err == nil {
154 t.Fatalf("CheckError(%d, %s) = nil, wanted error", test.code, test.body)
155 }
156 var terr *Error
157 if !errors.As(err, &terr) {
158 t.Fatalf("CheckError(%d, %s) = %v, wanted error type", test.code, test.body, err)
159 }
160
161 if terr.StatusCode != test.code {
162 t.Errorf("Incorrect status code, got %d, want %d", terr.StatusCode, test.code)
163 }
164
165 if terr.Error() != test.msg {
166 t.Errorf("Incorrect message, got %q, want %q", terr.Error(), test.msg)
167 }
168 }
169 }
170
171 func TestCheckErrorWithError(t *testing.T) {
172 tests := []struct {
173 name string
174 code int
175 errorBody string
176 msg string
177 }{{
178 name: "Invalid name error",
179 code: http.StatusBadRequest,
180 errorBody: `{"errors":[{"code":"NAME_INVALID","message":"a message for you"}],"StatusCode":400}`,
181 msg: "NAME_INVALID: a message for you",
182 }, {
183 name: "Only status code is provided",
184 code: http.StatusBadRequest,
185 errorBody: `{"StatusCode":400}`,
186 msg: "unexpected status code 400 Bad Request: {\"StatusCode\":400}",
187 }, {
188 name: "Multiple diagnostics",
189 code: http.StatusBadRequest,
190 errorBody: `{"errors":[{"code":"NAME_INVALID","message":"a message for you"}, {"code":"SIZE_INVALID","message":"another message for you", "detail": "with some details"}],"StatusCode":400,"Request":null}`,
191 msg: "multiple errors returned: NAME_INVALID: a message for you; SIZE_INVALID: another message for you; with some details",
192 }}
193
194 for _, test := range tests {
195 t.Run(test.name, func(t *testing.T) {
196 resp := &http.Response{
197 StatusCode: test.code,
198 Body: io.NopCloser(bytes.NewBuffer([]byte(test.errorBody))),
199 }
200
201 var terr *Error
202 if err := CheckError(resp, http.StatusOK); err == nil {
203 t.Errorf("CheckError(%d, %s) = nil, wanted error", test.code, test.errorBody)
204 } else if !errors.As(err, &terr) {
205 t.Errorf("CheckError(%d, %s) = %T, wanted *transport.Error", test.code, test.errorBody, err)
206 } else if diff := cmp.Diff(test.msg, err.Error()); diff != "" {
207 t.Errorf("CheckError(%d, %s).Error(); (-want +got) %s", test.code, test.errorBody, diff)
208 }
209 })
210 }
211 }
212
213 func TestBodyError(t *testing.T) {
214 expectedErr := errors.New("whoops")
215 resp := &http.Response{
216 StatusCode: http.StatusOK,
217 Body: &errReadCloser{expectedErr},
218 }
219 if err := CheckError(resp, http.StatusNotFound); err == nil {
220 t.Errorf("CheckError() = nil, wanted error %v", expectedErr)
221 } else if !errors.Is(err, expectedErr) {
222 t.Errorf("CheckError() = %v, wanted %v", err, expectedErr)
223 }
224 }
225
226 type errReadCloser struct {
227 err error
228 }
229
230 func (e *errReadCloser) Read(_ []byte) (int, error) {
231 return 0, e.err
232 }
233
234 func (e *errReadCloser) Close() error {
235 return e.err
236 }
237
View as plain text