github.com/ethersphere/bee/v2@v2.2.0/pkg/jsonhttp/jsonhttp_test.go (about) 1 // Copyright 2020 The Swarm Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package jsonhttp_test 6 7 import ( 8 "encoding/json" 9 "errors" 10 "net" 11 "net/http" 12 "net/http/httptest" 13 "reflect" 14 "testing" 15 16 "github.com/ethersphere/bee/v2/pkg/jsonhttp" 17 ) 18 19 func TestRespond_defaults(t *testing.T) { 20 t.Parallel() 21 22 w := httptest.NewRecorder() 23 24 jsonhttp.Respond(w, 0, nil) 25 26 statusCode := w.Result().StatusCode 27 wantCode := http.StatusOK 28 if statusCode != wantCode { 29 t.Errorf("got status code %d, want %d", statusCode, wantCode) 30 } 31 32 var m *jsonhttp.StatusResponse 33 34 if err := json.Unmarshal(w.Body.Bytes(), &m); err != nil { 35 t.Errorf("json unmarshal response body: %s", err) 36 } 37 38 if m.Code != wantCode { 39 t.Errorf("got message code %d, want %d", m.Code, wantCode) 40 } 41 42 wantMessage := http.StatusText(wantCode) 43 if m.Message != wantMessage { 44 t.Errorf("got message message %q, want %q", m.Message, wantMessage) 45 } 46 47 testContentType(t, w) 48 } 49 50 func TestRespond_statusResponse(t *testing.T) { 51 t.Parallel() 52 53 for _, tc := range []struct { 54 code int 55 }{ 56 {code: http.StatusContinue}, 57 {code: http.StatusSwitchingProtocols}, 58 {code: http.StatusOK}, 59 {code: http.StatusCreated}, 60 {code: http.StatusAccepted}, 61 {code: http.StatusNonAuthoritativeInfo}, 62 {code: http.StatusResetContent}, 63 {code: http.StatusPartialContent}, 64 {code: http.StatusMultipleChoices}, 65 {code: http.StatusMovedPermanently}, 66 {code: http.StatusFound}, 67 {code: http.StatusSeeOther}, 68 {code: http.StatusNotModified}, 69 {code: http.StatusUseProxy}, 70 {code: http.StatusTemporaryRedirect}, 71 {code: http.StatusPermanentRedirect}, 72 {code: http.StatusBadRequest}, 73 {code: http.StatusUnauthorized}, 74 {code: http.StatusPaymentRequired}, 75 {code: http.StatusForbidden}, 76 {code: http.StatusNotFound}, 77 {code: http.StatusMethodNotAllowed}, 78 {code: http.StatusNotAcceptable}, 79 {code: http.StatusProxyAuthRequired}, 80 {code: http.StatusRequestTimeout}, 81 {code: http.StatusConflict}, 82 {code: http.StatusGone}, 83 {code: http.StatusLengthRequired}, 84 {code: http.StatusPreconditionFailed}, 85 {code: http.StatusRequestEntityTooLarge}, 86 {code: http.StatusRequestURITooLong}, 87 {code: http.StatusUnsupportedMediaType}, 88 {code: http.StatusRequestedRangeNotSatisfiable}, 89 {code: http.StatusExpectationFailed}, 90 {code: http.StatusTeapot}, 91 {code: http.StatusUpgradeRequired}, 92 {code: http.StatusPreconditionRequired}, 93 {code: http.StatusTooManyRequests}, 94 {code: http.StatusRequestHeaderFieldsTooLarge}, 95 {code: http.StatusUnavailableForLegalReasons}, 96 {code: http.StatusInternalServerError}, 97 {code: http.StatusNotImplemented}, 98 {code: http.StatusBadGateway}, 99 {code: http.StatusServiceUnavailable}, 100 {code: http.StatusGatewayTimeout}, 101 {code: http.StatusHTTPVersionNotSupported}, 102 } { 103 w := httptest.NewRecorder() 104 105 jsonhttp.Respond(w, tc.code, nil) 106 107 statusCode := w.Result().StatusCode 108 if statusCode != tc.code { 109 t.Errorf("got status code %d, want %d", statusCode, tc.code) 110 } 111 112 var m *jsonhttp.StatusResponse 113 114 if err := json.Unmarshal(w.Body.Bytes(), &m); err != nil { 115 t.Errorf("json unmarshal response body: %s", err) 116 } 117 118 if m.Code != tc.code { 119 t.Errorf("got message code %d, want %d", m.Code, tc.code) 120 } 121 122 wantMessage := http.StatusText(tc.code) 123 if m.Message != wantMessage { 124 t.Errorf("got message message %q, want %q", m.Message, wantMessage) 125 } 126 127 testContentType(t, w) 128 } 129 } 130 131 func TestRespond_special(t *testing.T) { 132 t.Parallel() 133 134 for _, tc := range []struct { 135 name string 136 code int 137 response interface{} 138 wantMessage string 139 }{ 140 { 141 name: "string 200", 142 code: http.StatusOK, 143 response: "custom message", 144 wantMessage: "custom message", 145 }, 146 { 147 name: "string 404", 148 code: http.StatusNotFound, 149 response: "element not found", 150 wantMessage: "element not found", 151 }, 152 { 153 name: "error 400", 154 code: http.StatusBadRequest, 155 response: errors.New("test error"), 156 wantMessage: "test error", 157 }, 158 { 159 name: "error 500", 160 code: http.StatusInternalServerError, 161 response: errors.New("test error"), 162 wantMessage: "test error", 163 }, 164 { 165 name: "stringer 200", 166 code: http.StatusOK, 167 response: net.IPv4(127, 0, 0, 1), // net.IP implements Stringer interface 168 wantMessage: "127.0.0.1", 169 }, 170 { 171 name: "stringer 403", 172 code: http.StatusForbidden, 173 response: net.IPv4(2, 4, 8, 16), // net.IP implements Stringer interface 174 wantMessage: "2.4.8.16", 175 }, 176 } { 177 tc := tc 178 t.Run(tc.name, func(t *testing.T) { 179 t.Parallel() 180 181 w := httptest.NewRecorder() 182 183 jsonhttp.Respond(w, tc.code, tc.response) 184 185 statusCode := w.Result().StatusCode 186 if statusCode != tc.code { 187 t.Errorf("got status code %d, want %d", statusCode, tc.code) 188 } 189 190 var m *jsonhttp.StatusResponse 191 192 if err := json.Unmarshal(w.Body.Bytes(), &m); err != nil { 193 t.Errorf("json unmarshal response body: %s", err) 194 } 195 196 if m.Code != tc.code { 197 t.Errorf("got message code %d, want %d", m.Code, tc.code) 198 } 199 200 if m.Message != tc.wantMessage { 201 t.Errorf("got message message %q, want %q", m.Message, tc.wantMessage) 202 } 203 204 testContentType(t, w) 205 }) 206 } 207 } 208 209 func TestRespond_custom(t *testing.T) { 210 t.Parallel() 211 212 w := httptest.NewRecorder() 213 214 wantCode := http.StatusTeapot 215 216 type response struct { 217 Field1 string `json:"field1"` 218 Field2 int `json:"field2"` 219 } 220 221 r := response{ 222 Field1: "custom message", 223 Field2: 42, 224 } 225 jsonhttp.Respond(w, wantCode, r) 226 227 statusCode := w.Result().StatusCode 228 if statusCode != wantCode { 229 t.Errorf("got status code %d, want %d", statusCode, wantCode) 230 } 231 232 var m response 233 234 if err := json.Unmarshal(w.Body.Bytes(), &m); err != nil { 235 t.Errorf("json unmarshal response body: %s", err) 236 } 237 238 if !reflect.DeepEqual(m, r) { 239 t.Errorf("got response %+v, want %+v", m, r) 240 } 241 242 testContentType(t, w) 243 } 244 245 func TestStandardHTTPResponds(t *testing.T) { 246 t.Parallel() 247 248 for _, tc := range []struct { 249 f func(w http.ResponseWriter, response interface{}) 250 code int 251 }{ 252 {f: jsonhttp.Continue, code: http.StatusContinue}, 253 {f: jsonhttp.SwitchingProtocols, code: http.StatusSwitchingProtocols}, 254 {f: jsonhttp.OK, code: http.StatusOK}, 255 {f: jsonhttp.Created, code: http.StatusCreated}, 256 {f: jsonhttp.Accepted, code: http.StatusAccepted}, 257 {f: jsonhttp.NonAuthoritativeInfo, code: http.StatusNonAuthoritativeInfo}, 258 {f: jsonhttp.ResetContent, code: http.StatusResetContent}, 259 {f: jsonhttp.PartialContent, code: http.StatusPartialContent}, 260 {f: jsonhttp.MultipleChoices, code: http.StatusMultipleChoices}, 261 {f: jsonhttp.MovedPermanently, code: http.StatusMovedPermanently}, 262 {f: jsonhttp.Found, code: http.StatusFound}, 263 {f: jsonhttp.SeeOther, code: http.StatusSeeOther}, 264 {f: jsonhttp.NotModified, code: http.StatusNotModified}, 265 {f: jsonhttp.UseProxy, code: http.StatusUseProxy}, 266 {f: jsonhttp.TemporaryRedirect, code: http.StatusTemporaryRedirect}, 267 {f: jsonhttp.PermanentRedirect, code: http.StatusPermanentRedirect}, 268 {f: jsonhttp.BadRequest, code: http.StatusBadRequest}, 269 {f: jsonhttp.Unauthorized, code: http.StatusUnauthorized}, 270 {f: jsonhttp.PaymentRequired, code: http.StatusPaymentRequired}, 271 {f: jsonhttp.Forbidden, code: http.StatusForbidden}, 272 {f: jsonhttp.NotFound, code: http.StatusNotFound}, 273 {f: jsonhttp.MethodNotAllowed, code: http.StatusMethodNotAllowed}, 274 {f: jsonhttp.NotAcceptable, code: http.StatusNotAcceptable}, 275 {f: jsonhttp.ProxyAuthRequired, code: http.StatusProxyAuthRequired}, 276 {f: jsonhttp.RequestTimeout, code: http.StatusRequestTimeout}, 277 {f: jsonhttp.Conflict, code: http.StatusConflict}, 278 {f: jsonhttp.Gone, code: http.StatusGone}, 279 {f: jsonhttp.LengthRequired, code: http.StatusLengthRequired}, 280 {f: jsonhttp.PreconditionFailed, code: http.StatusPreconditionFailed}, 281 {f: jsonhttp.RequestEntityTooLarge, code: http.StatusRequestEntityTooLarge}, 282 {f: jsonhttp.RequestURITooLong, code: http.StatusRequestURITooLong}, 283 {f: jsonhttp.UnsupportedMediaType, code: http.StatusUnsupportedMediaType}, 284 {f: jsonhttp.RequestedRangeNotSatisfiable, code: http.StatusRequestedRangeNotSatisfiable}, 285 {f: jsonhttp.ExpectationFailed, code: http.StatusExpectationFailed}, 286 {f: jsonhttp.Teapot, code: http.StatusTeapot}, 287 {f: jsonhttp.UpgradeRequired, code: http.StatusUpgradeRequired}, 288 {f: jsonhttp.PreconditionRequired, code: http.StatusPreconditionRequired}, 289 {f: jsonhttp.TooManyRequests, code: http.StatusTooManyRequests}, 290 {f: jsonhttp.RequestHeaderFieldsTooLarge, code: http.StatusRequestHeaderFieldsTooLarge}, 291 {f: jsonhttp.UnavailableForLegalReasons, code: http.StatusUnavailableForLegalReasons}, 292 {f: jsonhttp.InternalServerError, code: http.StatusInternalServerError}, 293 {f: jsonhttp.NotImplemented, code: http.StatusNotImplemented}, 294 {f: jsonhttp.BadGateway, code: http.StatusBadGateway}, 295 {f: jsonhttp.ServiceUnavailable, code: http.StatusServiceUnavailable}, 296 {f: jsonhttp.GatewayTimeout, code: http.StatusGatewayTimeout}, 297 {f: jsonhttp.HTTPVersionNotSupported, code: http.StatusHTTPVersionNotSupported}, 298 } { 299 w := httptest.NewRecorder() 300 tc.f(w, nil) 301 var m *jsonhttp.StatusResponse 302 303 if err := json.Unmarshal(w.Body.Bytes(), &m); err != nil { 304 t.Errorf("json unmarshal response body: %s", err) 305 } 306 307 if m.Code != tc.code { 308 t.Errorf("expected message code %d, got %d", tc.code, m.Code) 309 } 310 311 if m.Message != http.StatusText(tc.code) { 312 t.Errorf("expected message message \"%s\", got \"%s\"", http.StatusText(tc.code), m.Message) 313 } 314 315 testContentType(t, w) 316 } 317 } 318 319 func TestPanicRespond(t *testing.T) { 320 t.Parallel() 321 322 w := httptest.NewRecorder() 323 324 defer func() { 325 err := recover() 326 if _, ok := err.(*json.UnsupportedTypeError); !ok { 327 t.Errorf("expected error from recover json.UnsupportedTypeError, got %#v", err) 328 } 329 }() 330 331 jsonhttp.Respond(w, http.StatusNotFound, map[bool]string{ 332 true: "", 333 }) 334 } 335 336 func testContentType(t *testing.T, r *httptest.ResponseRecorder) { 337 t.Helper() 338 339 if got := r.Header().Get("Content-Type"); got != jsonhttp.DefaultContentTypeHeader { 340 t.Errorf("got content type %q, want %q", got, jsonhttp.DefaultContentTypeHeader) 341 } 342 }