func TestTransformResponseNegotiate(t *testing.T) { invalid := []byte("aaaaa") uri, _ := url.Parse("http://localhost") testCases := []struct { Response *http.Response Data []byte Created bool Error bool ErrFn func(err error) bool ContentType string Called bool ExpectContentType string Decoder runtime.Decoder NegotiateErr error }{ { ContentType: "application/json", Response: &http.Response{ StatusCode: 401, Header: http.Header{"Content-Type": []string{"application/json"}}, Body: ioutil.NopCloser(bytes.NewReader(invalid)), }, Error: true, ErrFn: func(err error) bool { return err.Error() != "aaaaa" && apierrors.IsUnauthorized(err) }, }, { ContentType: "application/json", Response: &http.Response{ StatusCode: 401, Header: http.Header{"Content-Type": []string{"application/protobuf"}}, Body: ioutil.NopCloser(bytes.NewReader(invalid)), }, Decoder: testapi.Default.Codec(), Called: true, ExpectContentType: "application/protobuf", Error: true, ErrFn: func(err error) bool { return err.Error() != "aaaaa" && apierrors.IsUnauthorized(err) }, }, { ContentType: "application/json", Response: &http.Response{ StatusCode: 500, Header: http.Header{"Content-Type": []string{"application/,others"}}, }, Decoder: testapi.Default.Codec(), Error: true, ErrFn: func(err error) bool { return err.Error() == "Internal error occurred: mime: expected token after slash" && err.(apierrors.APIStatus).Status().Code == 500 }, }, { // no negotiation when no content type specified Response: &http.Response{ StatusCode: 200, Header: http.Header{"Content-Type": []string{"text/any"}}, Body: ioutil.NopCloser(bytes.NewReader(invalid)), }, Decoder: testapi.Default.Codec(), }, { // no negotiation when no response content type specified ContentType: "text/any", Response: &http.Response{ StatusCode: 200, Body: ioutil.NopCloser(bytes.NewReader(invalid)), }, Decoder: testapi.Default.Codec(), }, { // unrecognized content type is not handled ContentType: "application/json", Response: &http.Response{ StatusCode: 404, Header: http.Header{"Content-Type": []string{"application/unrecognized"}}, Body: ioutil.NopCloser(bytes.NewReader(invalid)), }, Decoder: testapi.Default.Codec(), NegotiateErr: fmt.Errorf("aaaa"), Called: true, ExpectContentType: "application/unrecognized", Error: true, ErrFn: func(err error) bool { return err.Error() != "aaaaa" && apierrors.IsNotFound(err) }, }, } for i, test := range testCases { serializers := defaultSerializers() negotiator := &renegotiator{ decoder: test.Decoder, err: test.NegotiateErr, } serializers.RenegotiatedDecoder = negotiator.invoke contentConfig := defaultContentConfig() contentConfig.ContentType = test.ContentType r := NewRequest(nil, "", uri, "", contentConfig, serializers, nil, nil) if test.Response.Body == nil { test.Response.Body = ioutil.NopCloser(bytes.NewReader([]byte{})) } result := r.transformResponse(test.Response, &http.Request{}) _, err := result.body, result.err hasErr := err != nil if hasErr != test.Error { t.Errorf("%d: unexpected error: %t %v", i, test.Error, err) continue } else if hasErr && test.Response.StatusCode > 399 { status, ok := err.(apierrors.APIStatus) if !ok { t.Errorf("%d: response should have been transformable into APIStatus: %v", i, err) continue } if int(status.Status().Code) != test.Response.StatusCode { t.Errorf("%d: status code did not match response: %#v", i, status.Status()) } } if test.ErrFn != nil && !test.ErrFn(err) { t.Errorf("%d: error function did not match: %v", i, err) } if negotiator.called != test.Called { t.Errorf("%d: negotiator called %t != %t", i, negotiator.called, test.Called) } if !test.Called { continue } if negotiator.contentType != test.ExpectContentType { t.Errorf("%d: unexpected content type: %s", i, negotiator.contentType) } } }
func TestRequestWatch(t *testing.T) { testCases := []struct { Request *Request Err bool ErrFn func(error) bool Empty bool }{ { Request: &Request{err: errors.New("bail")}, Err: true, }, { Request: &Request{baseURL: &url.URL{}, pathPrefix: "%"}, Err: true, }, { Request: &Request{ client: clientFunc(func(req *http.Request) (*http.Response, error) { return nil, errors.New("err") }), baseURL: &url.URL{}, }, Err: true, }, { Request: &Request{ content: defaultContentConfig(), serializers: defaultSerializers(), client: clientFunc(func(req *http.Request) (*http.Response, error) { return &http.Response{ StatusCode: http.StatusForbidden, Body: ioutil.NopCloser(bytes.NewReader([]byte{})), }, nil }), baseURL: &url.URL{}, }, Err: true, ErrFn: func(err error) bool { return apierrors.IsForbidden(err) }, }, { Request: &Request{ content: defaultContentConfig(), serializers: defaultSerializers(), client: clientFunc(func(req *http.Request) (*http.Response, error) { return &http.Response{ StatusCode: http.StatusUnauthorized, Body: ioutil.NopCloser(bytes.NewReader([]byte{})), }, nil }), baseURL: &url.URL{}, }, Err: true, ErrFn: func(err error) bool { return apierrors.IsUnauthorized(err) }, }, { Request: &Request{ content: defaultContentConfig(), serializers: defaultSerializers(), client: clientFunc(func(req *http.Request) (*http.Response, error) { return &http.Response{ StatusCode: http.StatusUnauthorized, Body: ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(testapi.Default.Codec(), &unversioned.Status{ Status: unversioned.StatusFailure, Reason: unversioned.StatusReasonUnauthorized, })))), }, nil }), baseURL: &url.URL{}, }, Err: true, ErrFn: func(err error) bool { return apierrors.IsUnauthorized(err) }, }, { Request: &Request{ serializers: defaultSerializers(), client: clientFunc(func(req *http.Request) (*http.Response, error) { return nil, io.EOF }), baseURL: &url.URL{}, }, Empty: true, }, { Request: &Request{ serializers: defaultSerializers(), client: clientFunc(func(req *http.Request) (*http.Response, error) { return nil, &url.Error{Err: io.EOF} }), baseURL: &url.URL{}, }, Empty: true, }, { Request: &Request{ serializers: defaultSerializers(), client: clientFunc(func(req *http.Request) (*http.Response, error) { return nil, errors.New("http: can't write HTTP request on broken connection") }), baseURL: &url.URL{}, }, Empty: true, }, { Request: &Request{ serializers: defaultSerializers(), client: clientFunc(func(req *http.Request) (*http.Response, error) { return nil, errors.New("foo: connection reset by peer") }), baseURL: &url.URL{}, }, Empty: true, }, } for i, testCase := range testCases { t.Logf("testcase %v", testCase.Request) testCase.Request.backoffMgr = &NoBackoff{} watch, err := testCase.Request.Watch() hasErr := err != nil if hasErr != testCase.Err { t.Errorf("%d: expected %t, got %t: %v", i, testCase.Err, hasErr, err) continue } if testCase.ErrFn != nil && !testCase.ErrFn(err) { t.Errorf("%d: error not valid: %v", i, err) } if hasErr && watch != nil { t.Errorf("%d: watch should be nil when error is returned", i) continue } if testCase.Empty { _, ok := <-watch.ResultChan() if ok { t.Errorf("%d: expected the watch to be empty: %#v", i, watch) } } } }
func TestTransformResponse(t *testing.T) { invalid := []byte("aaaaa") uri, _ := url.Parse("http://localhost") testCases := []struct { Response *http.Response Data []byte Created bool Error bool ErrFn func(err error) bool }{ {Response: &http.Response{StatusCode: 200}, Data: []byte{}}, {Response: &http.Response{StatusCode: 201}, Data: []byte{}, Created: true}, {Response: &http.Response{StatusCode: 199}, Error: true}, {Response: &http.Response{StatusCode: 500}, Error: true}, {Response: &http.Response{StatusCode: 422}, Error: true}, {Response: &http.Response{StatusCode: 409}, Error: true}, {Response: &http.Response{StatusCode: 404}, Error: true}, {Response: &http.Response{StatusCode: 401}, Error: true}, { Response: &http.Response{ StatusCode: 401, Header: http.Header{"Content-Type": []string{"application/json"}}, Body: ioutil.NopCloser(bytes.NewReader(invalid)), }, Error: true, ErrFn: func(err error) bool { return err.Error() != "aaaaa" && apierrors.IsUnauthorized(err) }, }, { Response: &http.Response{ StatusCode: 401, Header: http.Header{"Content-Type": []string{"text/any"}}, Body: ioutil.NopCloser(bytes.NewReader(invalid)), }, Error: true, ErrFn: func(err error) bool { return strings.Contains(err.Error(), "server has asked for the client to provide") && apierrors.IsUnauthorized(err) }, }, {Response: &http.Response{StatusCode: 403}, Error: true}, {Response: &http.Response{StatusCode: 200, Body: ioutil.NopCloser(bytes.NewReader(invalid))}, Data: invalid}, {Response: &http.Response{StatusCode: 200, Body: ioutil.NopCloser(bytes.NewReader(invalid))}, Data: invalid}, } for i, test := range testCases { r := NewRequest(nil, "", uri, "", defaultContentConfig(), defaultSerializers(), nil, nil) if test.Response.Body == nil { test.Response.Body = ioutil.NopCloser(bytes.NewReader([]byte{})) } result := r.transformResponse(test.Response, &http.Request{}) response, created, err := result.body, result.statusCode == http.StatusCreated, result.err hasErr := err != nil if hasErr != test.Error { t.Errorf("%d: unexpected error: %t %v", i, test.Error, err) } else if hasErr && test.Response.StatusCode > 399 { status, ok := err.(apierrors.APIStatus) if !ok { t.Errorf("%d: response should have been transformable into APIStatus: %v", i, err) continue } if int(status.Status().Code) != test.Response.StatusCode { t.Errorf("%d: status code did not match response: %#v", i, status.Status()) } } if test.ErrFn != nil && !test.ErrFn(err) { t.Errorf("%d: error function did not match: %v", i, err) } if !(test.Data == nil && response == nil) && !api.Semantic.DeepDerivative(test.Data, response) { t.Errorf("%d: unexpected response: %#v %#v", i, test.Data, response) } if test.Created != created { t.Errorf("%d: expected created %t, got %t", i, test.Created, created) } } }