github.com/xmidt-org/webpa-common@v1.11.9/xhttp/fanout/requestResponse_test.go (about)

     1  package fanout
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"net/http"
    10  	"net/http/httptest"
    11  	"net/url"
    12  	"strconv"
    13  	"testing"
    14  
    15  	"github.com/gorilla/mux"
    16  	"github.com/stretchr/testify/assert"
    17  	"github.com/stretchr/testify/require"
    18  )
    19  
    20  func testForwardBodyNoBody(t *testing.T, originalBody []byte) {
    21  	var (
    22  		assert  = assert.New(t)
    23  		require = require.New(t)
    24  
    25  		ctx      = context.WithValue(context.Background(), "foo", "bar")
    26  		original = httptest.NewRequest("GET", "/", nil)
    27  		fanout   = &http.Request{
    28  			Header:        http.Header{"Content-Type": []string{"foo"}},
    29  			ContentLength: 123,
    30  			Body:          ioutil.NopCloser(new(bytes.Reader)),
    31  			GetBody: func() (io.ReadCloser, error) {
    32  				assert.Fail("GetBody should not be called")
    33  				return nil, nil
    34  			},
    35  		}
    36  		rf = ForwardBody(true)
    37  	)
    38  
    39  	require.NotNil(rf)
    40  
    41  	returnedCtx, err := rf(ctx, original, fanout, originalBody)
    42  	assert.Equal(ctx, returnedCtx)
    43  	assert.NoError(err)
    44  	assert.Empty(fanout.Header.Get("Content-Type"))
    45  	assert.Zero(fanout.ContentLength)
    46  	assert.Nil(fanout.Body)
    47  	assert.Nil(fanout.GetBody)
    48  }
    49  
    50  func testForwardBodyFollowRedirects(t *testing.T) {
    51  	var (
    52  		assert  = assert.New(t)
    53  		require = require.New(t)
    54  
    55  		originalBody = "here is a lovely HTTP entity"
    56  
    57  		ctx      = context.WithValue(context.Background(), "foo", "bar")
    58  		original = httptest.NewRequest("GET", "/", nil)
    59  		fanout   = &http.Request{
    60  			Header:        http.Header{"Content-Type": []string{"foo"}},
    61  			ContentLength: 123,
    62  			Body:          ioutil.NopCloser(new(bytes.Reader)),
    63  			GetBody: func() (io.ReadCloser, error) {
    64  				assert.Fail("GetBody should have been updated")
    65  				return nil, nil
    66  			},
    67  		}
    68  		rf = ForwardBody(true)
    69  	)
    70  
    71  	require.NotNil(rf)
    72  	original.Header.Set("Content-Type", "text/plain")
    73  
    74  	returnedCtx, err := rf(ctx, original, fanout, []byte(originalBody))
    75  	assert.Equal(ctx, returnedCtx)
    76  	assert.NoError(err)
    77  	assert.Equal("text/plain", fanout.Header.Get("Content-Type"))
    78  	assert.Equal(int64(len(originalBody)), fanout.ContentLength)
    79  
    80  	require.NotNil(fanout.Body)
    81  	actualBody, err := ioutil.ReadAll(fanout.Body)
    82  	require.NoError(err)
    83  	assert.Equal(originalBody, string(actualBody))
    84  
    85  	require.NotNil(fanout.GetBody)
    86  	newBody, err := fanout.GetBody()
    87  	require.NoError(err)
    88  	require.NotNil(newBody)
    89  	actualBody, err = ioutil.ReadAll(newBody)
    90  	require.NoError(err)
    91  	assert.Equal(originalBody, string(actualBody))
    92  }
    93  
    94  func testForwardBodyNoFollowRedirects(t *testing.T) {
    95  	var (
    96  		assert  = assert.New(t)
    97  		require = require.New(t)
    98  
    99  		originalBody = "here is a lovely HTTP entity"
   100  
   101  		ctx      = context.WithValue(context.Background(), "foo", "bar")
   102  		original = httptest.NewRequest("GET", "/", nil)
   103  		fanout   = &http.Request{
   104  			Header:        http.Header{"Content-Type": []string{"foo"}},
   105  			ContentLength: 123,
   106  			Body:          ioutil.NopCloser(new(bytes.Reader)),
   107  			GetBody: func() (io.ReadCloser, error) {
   108  				assert.Fail("GetBody should have been updated")
   109  				return nil, nil
   110  			},
   111  		}
   112  		rf = ForwardBody(false)
   113  	)
   114  
   115  	require.NotNil(rf)
   116  	original.Header.Set("Content-Type", "text/plain")
   117  
   118  	returnedCtx, err := rf(ctx, original, fanout, []byte(originalBody))
   119  	assert.Equal(ctx, returnedCtx)
   120  	assert.NoError(err)
   121  	assert.Equal("text/plain", fanout.Header.Get("Content-Type"))
   122  	assert.Equal(int64(len(originalBody)), fanout.ContentLength)
   123  
   124  	require.NotNil(fanout.Body)
   125  	actualBody, err := ioutil.ReadAll(fanout.Body)
   126  	require.NoError(err)
   127  	assert.Equal(originalBody, string(actualBody))
   128  
   129  	assert.Nil(fanout.GetBody)
   130  }
   131  
   132  func TestForwardBody(t *testing.T) {
   133  	t.Run("NilBody", func(t *testing.T) { testForwardBodyNoBody(t, nil) })
   134  	t.Run("EmptyBody", func(t *testing.T) { testForwardBodyNoBody(t, make([]byte, 0)) })
   135  	t.Run("FollowRedirects=true", testForwardBodyFollowRedirects)
   136  	t.Run("FollowRedirects=false", testForwardBodyNoFollowRedirects)
   137  }
   138  
   139  func testForwardHeaders(t *testing.T, originalHeader http.Header, headersToCopy []string, expectedFanoutHeader http.Header) {
   140  	var (
   141  		assert  = assert.New(t)
   142  		require = require.New(t)
   143  		ctx     = context.WithValue(context.Background(), "foo", "bar")
   144  
   145  		original = &http.Request{
   146  			Header: originalHeader,
   147  		}
   148  
   149  		fanout = &http.Request{
   150  			Header: make(http.Header),
   151  		}
   152  
   153  		rf = ForwardHeaders(headersToCopy...)
   154  	)
   155  
   156  	require.NotNil(rf)
   157  	returnedCtx, err := rf(ctx, original, fanout, nil)
   158  	assert.Equal(ctx, returnedCtx)
   159  	assert.NoError(err)
   160  	assert.Equal(expectedFanoutHeader, fanout.Header)
   161  }
   162  
   163  func TestForwardHeaders(t *testing.T) {
   164  	testData := []struct {
   165  		originalHeader       http.Header
   166  		headersToCopy        []string
   167  		expectedFanoutHeader http.Header
   168  	}{
   169  		{
   170  			http.Header{"X-Test-1": []string{"foo"}, "X-Test-2": []string{"foo", "bar"}, "X-Test-3": []string{}},
   171  			nil,
   172  			http.Header{},
   173  		},
   174  		{
   175  			http.Header{"X-Test-1": []string{"foo"}, "X-Test-2": []string{"foo", "bar"}, "X-Test-3": []string{}},
   176  			[]string{"X-Does-Not-Exist"},
   177  			http.Header{},
   178  		},
   179  		{
   180  			http.Header{"X-Test-1": []string{"foo"}, "X-Test-2": []string{"foo", "bar"}, "X-Test-3": []string{}},
   181  			[]string{"X-Does-Not-Exist", "X-Test-1"},
   182  			http.Header{"X-Test-1": []string{"foo"}},
   183  		},
   184  		{
   185  			http.Header{"X-Test-1": []string{"foo"}, "X-Test-2": []string{"foo", "bar"}, "X-Test-3": []string{}},
   186  			[]string{"X-Does-Not-Exist", "x-test-1"},
   187  			http.Header{"X-Test-1": []string{"foo"}},
   188  		},
   189  		{
   190  			http.Header{"X-Test-1": []string{"foo"}, "X-Test-2": []string{"foo", "bar"}, "X-Test-3": []string{}},
   191  			[]string{"X-Test-1"},
   192  			http.Header{"X-Test-1": []string{"foo"}},
   193  		},
   194  		{
   195  			http.Header{"X-Test-1": []string{"foo"}, "X-Test-2": []string{"foo", "bar"}, "X-Test-3": []string{}},
   196  			[]string{"X-Test-3", "X-Test-1"},
   197  			http.Header{"X-Test-1": []string{"foo"}},
   198  		},
   199  		{
   200  			http.Header{"X-Test-1": []string{"foo"}, "X-Test-2": []string{"foo", "bar"}, "X-Test-3": []string{}},
   201  			[]string{"x-TeST-3", "X-tESt-1"},
   202  			http.Header{"X-Test-1": []string{"foo"}},
   203  		},
   204  		{
   205  			http.Header{"X-Test-1": []string{"foo"}, "X-Test-2": []string{"foo", "bar"}, "X-Test-3": []string{}},
   206  			[]string{"X-Test-3", "X-Test-1", "X-Test-2"},
   207  			http.Header{"X-Test-1": []string{"foo"}, "X-Test-2": []string{"foo", "bar"}},
   208  		},
   209  		{
   210  			http.Header{"X-Test-1": []string{"foo"}, "X-Test-2": []string{"foo", "bar"}, "X-Test-3": []string{}},
   211  			[]string{"X-TEST-3", "x-TEsT-1", "x-TesT-2"},
   212  			http.Header{"X-Test-1": []string{"foo"}, "X-Test-2": []string{"foo", "bar"}},
   213  		},
   214  	}
   215  
   216  	for i, record := range testData {
   217  		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
   218  			t.Logf("%#v", record)
   219  			testForwardHeaders(t, record.originalHeader, record.headersToCopy, record.expectedFanoutHeader)
   220  		})
   221  	}
   222  }
   223  
   224  func testUsePathPanics(t *testing.T) {
   225  	var (
   226  		assert  = assert.New(t)
   227  		require = require.New(t)
   228  
   229  		rf = UsePath("/foo")
   230  	)
   231  
   232  	require.NotNil(rf)
   233  	assert.Panics(func() {
   234  		rf(context.Background(), httptest.NewRequest("GET", "/", nil), new(http.Request), nil)
   235  	})
   236  }
   237  
   238  func testUsePath(t *testing.T, fanout http.Request) {
   239  	var (
   240  		assert  = assert.New(t)
   241  		require = require.New(t)
   242  
   243  		rf = UsePath("/api/v1/device/foo/bar")
   244  	)
   245  
   246  	require.NotNil(rf)
   247  
   248  	rf(context.Background(), httptest.NewRequest("GET", "/", nil), &fanout, nil)
   249  	assert.Equal("/api/v1/device/foo/bar", fanout.URL.Path)
   250  	assert.Empty(fanout.URL.RawPath)
   251  }
   252  
   253  func TestUsePath(t *testing.T) {
   254  	t.Run("Panics", testUsePathPanics)
   255  
   256  	testData := []http.Request{
   257  		{URL: new(url.URL)},
   258  		{URL: &url.URL{Host: "foobar.com:8080", Path: "/original"}},
   259  		{URL: &url.URL{Host: "foobar.com:8080", Path: "/something", RawPath: "this is a raw path"}},
   260  		{URL: &url.URL{Host: "foobar.com:8080", RawPath: "this is a raw path"}},
   261  	}
   262  
   263  	for i, fanout := range testData {
   264  		t.Run(strconv.Itoa(i), func(t *testing.T) {
   265  			testUsePath(t, fanout)
   266  		})
   267  	}
   268  }
   269  
   270  func testForwardVariableAsHeaderMissing(t *testing.T) {
   271  	var (
   272  		assert  = assert.New(t)
   273  		require = require.New(t)
   274  
   275  		ctx      = context.WithValue(context.Background(), "foo", "bar")
   276  		original = httptest.NewRequest("GET", "/", nil)
   277  		fanout   = httptest.NewRequest("GET", "/", nil)
   278  		rf       = ForwardVariableAsHeader("test", "X-Test")
   279  	)
   280  
   281  	require.NotNil(rf)
   282  	returnedCtx, err := rf(ctx, original, fanout, nil)
   283  	assert.Equal(ctx, returnedCtx)
   284  	assert.NoError(err)
   285  	assert.Equal("", fanout.Header.Get("X-Test"))
   286  }
   287  
   288  func testForwardVariableAsHeaderValue(t *testing.T) {
   289  	var (
   290  		assert  = assert.New(t)
   291  		require = require.New(t)
   292  
   293  		ctx       = context.WithValue(context.Background(), "foo", "bar")
   294  		variables = map[string]string{
   295  			"test": "foobar",
   296  		}
   297  
   298  		original = mux.SetURLVars(
   299  			httptest.NewRequest("GET", "/", nil),
   300  			variables,
   301  		)
   302  
   303  		fanout = httptest.NewRequest("GET", "/", nil)
   304  		rf     = ForwardVariableAsHeader("test", "X-Test")
   305  	)
   306  
   307  	require.NotNil(rf)
   308  	returnedCtx, err := rf(ctx, original, fanout, nil)
   309  	assert.Equal(ctx, returnedCtx)
   310  	assert.NoError(err)
   311  	assert.Equal("foobar", fanout.Header.Get("X-Test"))
   312  }
   313  
   314  func TestForwardVariableAsHeader(t *testing.T) {
   315  	t.Run("Missing", testForwardVariableAsHeaderMissing)
   316  	t.Run("Value", testForwardVariableAsHeaderValue)
   317  }
   318  
   319  func testReturnHeaders(t *testing.T, fanoutResponse *http.Response, headersToCopy []string, expectedResponseHeader http.Header) {
   320  	var (
   321  		assert  = assert.New(t)
   322  		require = require.New(t)
   323  		ctx     = context.WithValue(context.Background(), "foo", "bar")
   324  
   325  		response = httptest.NewRecorder()
   326  		rf       = ReturnHeaders(headersToCopy...)
   327  	)
   328  
   329  	require.NotNil(rf)
   330  	assert.Equal(ctx, rf(ctx, response, Result{Response: fanoutResponse}))
   331  	assert.Equal(expectedResponseHeader, response.Header())
   332  }
   333  
   334  func TestReturnHeaders(t *testing.T) {
   335  	testData := []struct {
   336  		fanoutResponse         *http.Response
   337  		headersToCopy          []string
   338  		expectedResponseHeader http.Header
   339  	}{
   340  		{
   341  			nil,
   342  			nil,
   343  			http.Header{},
   344  		},
   345  		{
   346  			&http.Response{},
   347  			nil,
   348  			http.Header{},
   349  		},
   350  		{
   351  			&http.Response{Header: http.Header{"X-Test-1": []string{"foo"}}},
   352  			nil,
   353  			http.Header{},
   354  		},
   355  		{
   356  			&http.Response{Header: http.Header{"X-Test-1": []string{"foo"}, "X-Test-2": []string{"foo", "bar"}, "X-Test-3": []string{}}},
   357  			[]string{"X-Does-Not-Exist"},
   358  			http.Header{},
   359  		},
   360  		{
   361  			&http.Response{Header: http.Header{"X-Test-1": []string{"foo"}, "X-Test-2": []string{"foo", "bar"}, "X-Test-3": []string{}}},
   362  			[]string{"X-Does-Not-Exist", "X-Test-1"},
   363  			http.Header{"X-Test-1": []string{"foo"}},
   364  		},
   365  		{
   366  			&http.Response{Header: http.Header{"X-Test-1": []string{"foo"}, "X-Test-2": []string{"foo", "bar"}, "X-Test-3": []string{}}},
   367  			[]string{"X-Does-Not-Exist", "x-TeSt-1"},
   368  			http.Header{"X-Test-1": []string{"foo"}},
   369  		},
   370  		{
   371  			&http.Response{Header: http.Header{"X-Test-1": []string{"foo"}, "X-Test-2": []string{"foo", "bar"}, "X-Test-3": []string{}}},
   372  			[]string{"X-Test-3", "X-Test-1"},
   373  			http.Header{"X-Test-1": []string{"foo"}},
   374  		},
   375  		{
   376  			&http.Response{Header: http.Header{"X-Test-1": []string{"foo"}, "X-Test-2": []string{"foo", "bar"}, "X-Test-3": []string{}}},
   377  			[]string{"x-TeST-3", "X-tESt-1"},
   378  			http.Header{"X-Test-1": []string{"foo"}},
   379  		},
   380  		{
   381  			&http.Response{Header: http.Header{"X-Test-1": []string{"foo"}, "X-Test-2": []string{"foo", "bar"}, "X-Test-3": []string{}}},
   382  			[]string{"X-Test-3", "X-Test-1", "X-Test-2"},
   383  			http.Header{"X-Test-1": []string{"foo"}, "X-Test-2": []string{"foo", "bar"}},
   384  		},
   385  		{
   386  			&http.Response{Header: http.Header{"X-Test-1": []string{"foo"}, "X-Test-2": []string{"foo", "bar"}, "X-Test-3": []string{}}},
   387  			[]string{"X-TEST-3", "x-TEsT-1", "x-TesT-2"},
   388  			http.Header{"X-Test-1": []string{"foo"}, "X-Test-2": []string{"foo", "bar"}},
   389  		},
   390  	}
   391  
   392  	for i, record := range testData {
   393  		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
   394  			t.Logf("%#v", record)
   395  			testReturnHeaders(t, record.fanoutResponse, record.headersToCopy, record.expectedResponseHeader)
   396  		})
   397  	}
   398  }
   399  
   400  func testReturnHeadersWithPrefix(t *testing.T, fanoutResponse *http.Response, headerPrefixToCopy []string, expectedResponseHeader http.Header) {
   401  	var (
   402  		assert  = assert.New(t)
   403  		require = require.New(t)
   404  		ctx     = context.WithValue(context.Background(), "foo", "bar")
   405  
   406  		response = httptest.NewRecorder()
   407  		rf       = ReturnHeadersWithPrefix(headerPrefixToCopy...)
   408  	)
   409  
   410  	require.NotNil(rf)
   411  	assert.Equal(ctx, rf(ctx, response, Result{Response: fanoutResponse}))
   412  	assert.Equal(expectedResponseHeader, response.Header())
   413  }
   414  
   415  func TestReturnHeadersWithPrefix(t *testing.T) {
   416  	testData := []struct {
   417  		fanoutResponse         *http.Response
   418  		prefixs                []string
   419  		expectedResponseHeader http.Header
   420  	}{
   421  		{
   422  			nil,
   423  			nil,
   424  			http.Header{},
   425  		},
   426  		{
   427  			&http.Response{},
   428  			nil,
   429  			http.Header{},
   430  		},
   431  		{
   432  			&http.Response{Header: http.Header{"X-Test-1": []string{"foo"}}},
   433  			nil,
   434  			http.Header{},
   435  		},
   436  		{
   437  			&http.Response{Header: http.Header{"X-Test-1": []string{"foo"}, "X-Test-2": []string{"foo", "bar"}, "X-Test-3": []string{}}},
   438  			[]string{"X-Does-Not-Exist"},
   439  			http.Header{},
   440  		},
   441  		{
   442  			&http.Response{Header: http.Header{"X-Test-1": []string{"foo"}, "X-Test-2": []string{"foo", "bar"}, "X-Test-3": []string{}}},
   443  			[]string{"X-Does-Not-Exist", "X-Test-1"},
   444  			http.Header{"X-Test-1": []string{"foo"}},
   445  		},
   446  		{
   447  			&http.Response{Header: http.Header{"X-Test-1": []string{"foo"}, "X-Test-2": []string{"foo", "bar"}, "X-Test-3": []string{}}},
   448  			[]string{"X-Does-Not-Exist", "x-TeSt-1"},
   449  			http.Header{"X-Test-1": []string{"foo"}},
   450  		},
   451  		{
   452  			&http.Response{Header: http.Header{"X-Test-1": []string{"foo"}, "X-Test-2": []string{"foo", "bar"}, "X-Test-3": []string{}}},
   453  			[]string{"X-Test-3", "X-Test-1"},
   454  			http.Header{"X-Test-1": []string{"foo"}},
   455  		},
   456  		{
   457  			&http.Response{Header: http.Header{"X-Test-1": []string{"foo"}, "X-Test-2": []string{"foo", "bar"}, "X-Test-3": []string{}}},
   458  			[]string{"x-TeST-3", "X-tESt-1"},
   459  			http.Header{"X-Test-1": []string{"foo"}},
   460  		},
   461  		{
   462  			&http.Response{Header: http.Header{"X-Test-1": []string{"foo"}, "X-Test-2": []string{"foo", "bar"}, "X-Test-3": []string{}}},
   463  			[]string{"X-Test-3", "X-Test-1", "X-Test-2"},
   464  			http.Header{"X-Test-1": []string{"foo"}, "X-Test-2": []string{"foo", "bar"}},
   465  		},
   466  		{
   467  			&http.Response{Header: http.Header{"X-Test-1": []string{"foo"}, "X-Test-2": []string{"foo", "bar"}, "X-Test-3": []string{}}},
   468  			[]string{"X-TEST-3", "x-TEsT-1", "x-TesT-2"},
   469  			http.Header{"X-Test-1": []string{"foo"}, "X-Test-2": []string{"foo", "bar"}},
   470  		},
   471  		{
   472  			&http.Response{Header: http.Header{"X-Test-1": []string{"foo"}, "X-Test-2": []string{"foo", "bar"}, "X-Test-3": []string{}}},
   473  			[]string{"X-TEST"},
   474  			http.Header{"X-Test-1": []string{"foo"}, "X-Test-2": []string{"foo", "bar"}},
   475  		},
   476  	}
   477  
   478  	for i, record := range testData {
   479  		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
   480  			t.Logf("%#v", record)
   481  			testReturnHeadersWithPrefix(t, record.fanoutResponse, record.prefixs, record.expectedResponseHeader)
   482  		})
   483  	}
   484  }