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  }