TestWithoutFollowingRedirects verifies that WithoutFollowingRedirects stops the request at the redirect and surfaces an HTTPError rather than following through. It also proves the redirect is not followed by recording every method the transport sees: without the option the transport would receive a
(t *testing.T)
| 364 | // the correct behaviour for repo delete: the caller learns the operation failed rather than |
| 365 | // receiving a misleading success. |
| 366 | func TestWithoutFollowingRedirects(t *testing.T) { |
| 367 | newCountingTransport := func(methods *[]string) http.RoundTripper { |
| 368 | return roundTripFunc(func(req *http.Request) (*http.Response, error) { |
| 369 | *methods = append(*methods, req.Method) |
| 370 | resp := &http.Response{ |
| 371 | StatusCode: http.StatusMovedPermanently, |
| 372 | Header: http.Header{"Location": []string{"https://api.github.com/repos/OWNER/RENAMED"}}, |
| 373 | Body: io.NopCloser(bytes.NewBufferString("")), |
| 374 | Request: req, |
| 375 | } |
| 376 | return resp, nil |
| 377 | }) |
| 378 | } |
| 379 | |
| 380 | t.Run("without option: redirect is followed, DELETE becomes GET", func(t *testing.T) { |
| 381 | var methods []string |
| 382 | transport := newCountingTransport(&methods) |
| 383 | // Second request (the followed redirect) must also be handled by the transport. |
| 384 | redirectTransport := redirectFollowingTransport{ |
| 385 | inner: transport, |
| 386 | methods: &methods, |
| 387 | } |
| 388 | client := NewClientFromHTTP(&http.Client{Transport: redirectTransport}) |
| 389 | |
| 390 | _, _ = client.Request("github.com", http.MethodDelete, "repos/OWNER/REPO", nil) |
| 391 | |
| 392 | // The transport sees DELETE then GET because Go's default policy demotes DELETE to GET. |
| 393 | require.GreaterOrEqual(t, len(methods), 2) |
| 394 | assert.Equal(t, http.MethodDelete, methods[0]) |
| 395 | assert.Equal(t, http.MethodGet, methods[1]) |
| 396 | }) |
| 397 | |
| 398 | t.Run("with option: redirect is not followed, HTTPError carries redirect status", func(t *testing.T) { |
| 399 | var methods []string |
| 400 | client := NewClientFromHTTP(&http.Client{Transport: newCountingTransport(&methods)}) |
| 401 | |
| 402 | _, err := client.Request("github.com", http.MethodDelete, "repos/OWNER/REPO", nil, WithoutFollowingRedirects()) |
| 403 | require.Error(t, err) |
| 404 | |
| 405 | var httpErr HTTPError |
| 406 | require.ErrorAs(t, err, &httpErr) |
| 407 | assert.Equal(t, http.StatusMovedPermanently, httpErr.StatusCode) |
| 408 | |
| 409 | // Transport is only asked for the original DELETE; the redirect is never followed. |
| 410 | assert.Equal(t, []string{http.MethodDelete}, methods) |
| 411 | }) |
| 412 | } |
| 413 | |
| 414 | // roundTripFunc lets a plain function satisfy http.RoundTripper. |
| 415 | type roundTripFunc func(*http.Request) (*http.Response, error) |
nothing calls this directly
no test coverage detected