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 }