github.com/ethersphere/bee/v2@v2.2.0/pkg/jsonhttp/jsonhttptest/jsonhttptest_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 jsonhttptest_test
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"encoding/json"
    11  	"errors"
    12  	"fmt"
    13  	"io"
    14  	"mime"
    15  	"mime/multipart"
    16  	"net/http"
    17  	"net/http/httptest"
    18  	"strconv"
    19  	"strings"
    20  	"testing"
    21  
    22  	"github.com/ethersphere/bee/v2/pkg/jsonhttp"
    23  	"github.com/ethersphere/bee/v2/pkg/jsonhttp/jsonhttptest"
    24  )
    25  
    26  func TestRequest_statusCode(t *testing.T) {
    27  	t.Parallel()
    28  
    29  	c, endpoint := newClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    30  		w.WriteHeader(http.StatusBadRequest)
    31  	}))
    32  
    33  	assert(t, testResult{}, func(m *mock) {
    34  		jsonhttptest.Request(m, c, http.MethodGet, endpoint, http.StatusBadRequest)
    35  	})
    36  
    37  	tr := testResult{
    38  		errors: []string{`got response status 400 Bad Request, want 200 OK`},
    39  	}
    40  	assert(t, tr, func(m *mock) {
    41  		jsonhttptest.Request(m, c, http.MethodGet, endpoint, http.StatusOK)
    42  	})
    43  }
    44  
    45  func TestRequest_method(t *testing.T) {
    46  	t.Parallel()
    47  
    48  	c, endpoint := newClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    49  		if r.Method != http.MethodGet {
    50  			w.WriteHeader(http.StatusMethodNotAllowed)
    51  			return
    52  		}
    53  	}))
    54  
    55  	assert(t, testResult{}, func(m *mock) {
    56  		jsonhttptest.Request(m, c, http.MethodGet, endpoint, http.StatusOK)
    57  	})
    58  
    59  	assert(t, testResult{}, func(m *mock) {
    60  		jsonhttptest.Request(m, c, http.MethodPost, endpoint, http.StatusMethodNotAllowed)
    61  	})
    62  }
    63  
    64  func TestRequest_url(t *testing.T) {
    65  	t.Parallel()
    66  
    67  	c, endpoint := newClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    68  		if r.URL.Path != "/" {
    69  			w.WriteHeader(http.StatusNotFound)
    70  		}
    71  	}))
    72  
    73  	assert(t, testResult{}, func(m *mock) {
    74  		jsonhttptest.Request(m, c, http.MethodGet, endpoint, http.StatusOK)
    75  	})
    76  
    77  	assert(t, testResult{}, func(m *mock) {
    78  		jsonhttptest.Request(m, c, http.MethodPost, endpoint+"/bzz", http.StatusNotFound)
    79  	})
    80  }
    81  
    82  func TestRequest_responseHeader(t *testing.T) {
    83  	t.Parallel()
    84  
    85  	headerName := "Swarm-Header"
    86  	headerValue := "Digital Freedom Now"
    87  
    88  	t.Run("with header", func(t *testing.T) {
    89  		t.Parallel()
    90  
    91  		c, endpoint := newClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    92  			w.Header().Set(headerName, headerValue)
    93  		}))
    94  
    95  		assert(t, testResult{}, func(m *mock) {
    96  			jsonhttptest.Request(m, c, http.MethodGet, endpoint, http.StatusOK,
    97  				jsonhttptest.WithExpectedResponseHeader(headerName, headerValue),
    98  				jsonhttptest.WithNonEmptyResponseHeader(headerName),
    99  			)
   100  		})
   101  	})
   102  
   103  	t.Run("without header", func(t *testing.T) {
   104  		t.Parallel()
   105  
   106  		c, endpoint := newClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
   107  
   108  		tr := testResult{
   109  			errors: []string{
   110  				fmt.Sprintf(`header key=[%v] should be set`, headerName),
   111  				fmt.Sprintf(`header values for key=[%v] not as expected, got: [], want [%v]`, headerName, headerValue),
   112  			},
   113  		}
   114  
   115  		assert(t, tr, func(m *mock) {
   116  			jsonhttptest.Request(m, c, http.MethodGet, endpoint, http.StatusOK,
   117  				jsonhttptest.WithNonEmptyResponseHeader(headerName),
   118  				jsonhttptest.WithExpectedResponseHeader(headerName, headerValue),
   119  			)
   120  		})
   121  	})
   122  }
   123  
   124  func TestWithContext(t *testing.T) {
   125  	t.Parallel()
   126  
   127  	c, endpoint := newClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
   128  
   129  	ctx, cancel := context.WithCancel(context.Background())
   130  	cancel() // cancel the context to detect the fatal error
   131  
   132  	tr := testResult{
   133  		fatal: fmt.Sprintf("Get %q: context canceled", endpoint),
   134  	}
   135  	assert(t, tr, func(m *mock) {
   136  		jsonhttptest.Request(m, c, http.MethodGet, endpoint, http.StatusOK,
   137  			jsonhttptest.WithContext(ctx),
   138  		)
   139  	})
   140  }
   141  
   142  func TestWithRequestBody(t *testing.T) {
   143  	t.Parallel()
   144  
   145  	wantBody := []byte("somebody")
   146  	var gotBody []byte
   147  	c, endpoint := newClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   148  		var err error
   149  		gotBody, err = io.ReadAll(r.Body)
   150  		if err != nil {
   151  			w.WriteHeader(http.StatusInternalServerError)
   152  		}
   153  	}))
   154  
   155  	assert(t, testResult{}, func(m *mock) {
   156  		jsonhttptest.Request(m, c, http.MethodPost, endpoint, http.StatusOK,
   157  			jsonhttptest.WithRequestBody(bytes.NewReader(wantBody)),
   158  		)
   159  	})
   160  	if !bytes.Equal(gotBody, wantBody) {
   161  		t.Errorf("got body %q, want %q", string(gotBody), string(wantBody))
   162  	}
   163  }
   164  
   165  func TestWithJSONRequestBody(t *testing.T) {
   166  	t.Parallel()
   167  
   168  	type response struct {
   169  		Message string `json:"message"`
   170  	}
   171  	message := "text"
   172  
   173  	wantBody := response{
   174  		Message: message,
   175  	}
   176  	var gotBody response
   177  	c, endpoint := newClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   178  		v, err := io.ReadAll(r.Body)
   179  		if err != nil {
   180  			jsonhttp.InternalServerError(w, err)
   181  			return
   182  		}
   183  		if err := json.Unmarshal(v, &gotBody); err != nil {
   184  			jsonhttp.BadRequest(w, err)
   185  			return
   186  		}
   187  	}))
   188  
   189  	assert(t, testResult{}, func(m *mock) {
   190  		jsonhttptest.Request(m, c, http.MethodPost, endpoint, http.StatusOK,
   191  			jsonhttptest.WithJSONRequestBody(wantBody),
   192  		)
   193  	})
   194  	if gotBody.Message != message {
   195  		t.Errorf("got message %q, want %q", gotBody.Message, message)
   196  	}
   197  }
   198  
   199  func TestWithMultipartRequest(t *testing.T) {
   200  	t.Parallel()
   201  
   202  	wantBody := []byte("somebody")
   203  	filename := "swarm.jpg"
   204  	contentType := "image/jpeg"
   205  	var gotBody []byte
   206  	var gotContentDisposition, gotContentType string
   207  	c, endpoint := newClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   208  		mediaType, params, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
   209  		if err != nil {
   210  			jsonhttp.BadRequest(w, err)
   211  			return
   212  		}
   213  		if strings.HasPrefix(mediaType, "multipart/") {
   214  			mr := multipart.NewReader(r.Body, params["boundary"])
   215  
   216  			p, err := mr.NextPart()
   217  			if err != nil {
   218  				if errors.Is(err, io.EOF) {
   219  					return
   220  				}
   221  				jsonhttp.BadRequest(w, err)
   222  				return
   223  			}
   224  			gotContentDisposition = p.Header.Get("Content-Disposition")
   225  			gotContentType = p.Header.Get("Content-Type")
   226  			gotBody, err = io.ReadAll(p)
   227  			if err != nil {
   228  				jsonhttp.BadRequest(w, err)
   229  				return
   230  			}
   231  		}
   232  	}))
   233  
   234  	assert(t, testResult{}, func(m *mock) {
   235  		jsonhttptest.Request(m, c, http.MethodPost, endpoint, http.StatusOK,
   236  			jsonhttptest.WithMultipartRequest(bytes.NewReader(wantBody), len(wantBody), filename, contentType),
   237  		)
   238  	})
   239  	if !bytes.Equal(gotBody, wantBody) {
   240  		t.Errorf("got body %q, want %q", string(gotBody), string(wantBody))
   241  	}
   242  	if gotContentType != contentType {
   243  		t.Errorf("got content type %q, want %q", gotContentType, contentType)
   244  	}
   245  	if contentDisposition := fmt.Sprintf("form-data; name=%q", filename); gotContentDisposition != contentDisposition {
   246  		t.Errorf("got content disposition %q, want %q", gotContentDisposition, contentDisposition)
   247  	}
   248  }
   249  
   250  func TestWithRequestHeader(t *testing.T) {
   251  	t.Parallel()
   252  
   253  	headerName := "Swarm-Header"
   254  	headerValue := "somevalue"
   255  	var gotValue string
   256  
   257  	c, endpoint := newClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   258  		gotValue = r.Header.Get(headerName)
   259  	}))
   260  
   261  	assert(t, testResult{}, func(m *mock) {
   262  		jsonhttptest.Request(m, c, http.MethodPost, endpoint, http.StatusOK,
   263  			jsonhttptest.WithRequestHeader(headerName, headerValue),
   264  		)
   265  	})
   266  	if gotValue != headerValue {
   267  		t.Errorf("got header %q, want %q", gotValue, headerValue)
   268  	}
   269  }
   270  
   271  func TestWithExpectedContentLength(t *testing.T) {
   272  	t.Parallel()
   273  
   274  	body := []byte("Digital Freedom Now")
   275  
   276  	c, endpoint := newClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   277  		w.Header().Set("Content-Length", strconv.Itoa(len(body)))
   278  		_, err := w.Write(body)
   279  		if err != nil {
   280  			t.Error(err)
   281  		}
   282  	}))
   283  
   284  	assert(t, testResult{}, func(m *mock) {
   285  		jsonhttptest.Request(m, c, http.MethodGet, endpoint, http.StatusOK,
   286  			jsonhttptest.WithExpectedContentLength(len(body)),
   287  		)
   288  	})
   289  
   290  	tr := testResult{
   291  		errors: []string{
   292  			"header values for key=[Content-Length] not as expected, got: [19], want [100]",
   293  			"http.Response.ContentLength not as expected, got 19, want 100",
   294  		},
   295  	}
   296  	assert(t, tr, func(m *mock) {
   297  		jsonhttptest.Request(m, c, http.MethodGet, endpoint, http.StatusOK,
   298  			jsonhttptest.WithExpectedContentLength(100),
   299  		)
   300  	})
   301  }
   302  
   303  func TestWithExpectedResponse(t *testing.T) {
   304  	t.Parallel()
   305  
   306  	body := []byte("something to want")
   307  
   308  	c, endpoint := newClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   309  		_, err := w.Write(body)
   310  		if err != nil {
   311  			jsonhttp.InternalServerError(w, err)
   312  		}
   313  	}))
   314  
   315  	assert(t, testResult{}, func(m *mock) {
   316  		jsonhttptest.Request(m, c, http.MethodGet, endpoint, http.StatusOK,
   317  			jsonhttptest.WithExpectedResponse(body),
   318  		)
   319  	})
   320  
   321  	tr := testResult{
   322  		errors: []string{`got response "something to want", want "invalid"`},
   323  	}
   324  	assert(t, tr, func(m *mock) {
   325  		jsonhttptest.Request(m, c, http.MethodGet, endpoint, http.StatusOK,
   326  			jsonhttptest.WithExpectedResponse([]byte("invalid")),
   327  		)
   328  	})
   329  }
   330  
   331  func TestWithExpectedJSONResponse(t *testing.T) {
   332  	t.Parallel()
   333  
   334  	type response struct {
   335  		Message string `json:"message"`
   336  	}
   337  
   338  	want := response{
   339  		Message: "text",
   340  	}
   341  
   342  	c, endpoint := newClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   343  		jsonhttp.OK(w, want)
   344  	}))
   345  
   346  	assert(t, testResult{}, func(m *mock) {
   347  		jsonhttptest.Request(m, c, http.MethodGet, endpoint, http.StatusOK,
   348  			jsonhttptest.WithExpectedJSONResponse(want),
   349  		)
   350  	})
   351  
   352  	tr := testResult{
   353  		errors: []string{`got json response "{\"message\":\"text\"}", want "{\"message\":\"invalid\"}"`},
   354  	}
   355  	assert(t, tr, func(m *mock) {
   356  		jsonhttptest.Request(m, c, http.MethodGet, endpoint, http.StatusOK,
   357  			jsonhttptest.WithExpectedJSONResponse(response{
   358  				Message: "invalid",
   359  			}),
   360  		)
   361  	})
   362  }
   363  
   364  func TestWithUnmarhalJSONResponse(t *testing.T) {
   365  	t.Parallel()
   366  
   367  	message := "text"
   368  
   369  	c, endpoint := newClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   370  		jsonhttp.OK(w, message)
   371  	}))
   372  
   373  	var r jsonhttp.StatusResponse
   374  	assert(t, testResult{}, func(m *mock) {
   375  		jsonhttptest.Request(m, c, http.MethodGet, endpoint, http.StatusOK,
   376  			jsonhttptest.WithUnmarshalJSONResponse(&r),
   377  		)
   378  	})
   379  	if r.Message != message {
   380  		t.Errorf("got message %q, want %q", r.Message, message)
   381  	}
   382  }
   383  
   384  func TestWithPutResponseBody(t *testing.T) {
   385  	t.Parallel()
   386  
   387  	wantBody := []byte("somebody")
   388  
   389  	c, endpoint := newClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   390  		_, err := w.Write(wantBody)
   391  		if err != nil {
   392  			jsonhttp.InternalServerError(w, err)
   393  		}
   394  	}))
   395  
   396  	var gotBody []byte
   397  	assert(t, testResult{}, func(m *mock) {
   398  		jsonhttptest.Request(m, c, http.MethodGet, endpoint, http.StatusOK,
   399  			jsonhttptest.WithPutResponseBody(&gotBody),
   400  		)
   401  	})
   402  	if !bytes.Equal(gotBody, wantBody) {
   403  		t.Errorf("got body %q, want %q", string(gotBody), string(wantBody))
   404  	}
   405  }
   406  
   407  func TestWithNoResponseBody(t *testing.T) {
   408  	t.Parallel()
   409  
   410  	c, endpoint := newClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   411  		if r.URL.Path != "/" {
   412  			fmt.Fprint(w, "not found")
   413  		}
   414  	}))
   415  
   416  	assert(t, testResult{}, func(m *mock) {
   417  		jsonhttptest.Request(m, c, http.MethodGet, endpoint, http.StatusOK,
   418  			jsonhttptest.WithNoResponseBody(),
   419  		)
   420  	})
   421  
   422  	tr := testResult{
   423  		errors: []string{`got response body "not found", want none`},
   424  	}
   425  	assert(t, tr, func(m *mock) {
   426  		jsonhttptest.Request(m, c, http.MethodGet, endpoint+"/bzz", http.StatusOK,
   427  			jsonhttptest.WithNoResponseBody(),
   428  		)
   429  	})
   430  }
   431  
   432  func newClient(t *testing.T, handler http.Handler) (c *http.Client, endpoint string) {
   433  	t.Helper()
   434  
   435  	s := httptest.NewServer(handler)
   436  	t.Cleanup(s.Close)
   437  	return s.Client(), s.URL
   438  }