summaryrefslogtreecommitdiffstats
path: root/pkg/v1/remote/transport/error_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/v1/remote/transport/error_test.go')
-rw-r--r--pkg/v1/remote/transport/error_test.go236
1 files changed, 236 insertions, 0 deletions
diff --git a/pkg/v1/remote/transport/error_test.go b/pkg/v1/remote/transport/error_test.go
new file mode 100644
index 0000000..e42ce3a
--- /dev/null
+++ b/pkg/v1/remote/transport/error_test.go
@@ -0,0 +1,236 @@
+// Copyright 2018 Google LLC All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package transport
+
+import (
+ "bytes"
+ "errors"
+ "io"
+ "net/http"
+ "net/url"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+)
+
+func TestTemporary(t *testing.T) {
+ tests := []struct {
+ error *Error
+ retry bool
+ }{{
+ error: &Error{},
+ retry: false,
+ }, {
+ error: &Error{
+ Errors: []Diagnostic{{
+ Code: BlobUploadInvalidErrorCode,
+ }},
+ },
+ retry: true,
+ }, {
+ error: &Error{
+ Errors: []Diagnostic{{
+ Code: BlobUploadInvalidErrorCode,
+ }, {
+ Code: DeniedErrorCode,
+ }},
+ },
+ retry: false,
+ }, {
+ error: &Error{
+ Errors: []Diagnostic{{
+ Code: TooManyRequestsErrorCode,
+ }},
+ },
+ retry: true,
+ }, {
+ error: &Error{
+ Errors: []Diagnostic{{
+ Code: UnavailableErrorCode,
+ }},
+ },
+ retry: true,
+ }, {
+ error: &Error{
+ StatusCode: http.StatusInternalServerError,
+ },
+ retry: true,
+ }}
+
+ for _, test := range tests {
+ retry := test.error.Temporary()
+
+ if test.retry != retry {
+ t.Errorf("Temporary(%s) = %t, wanted %t", test.error, retry, test.retry)
+ }
+ }
+}
+
+func TestCheckErrorNil(t *testing.T) {
+ tests := []int{
+ http.StatusOK,
+ http.StatusAccepted,
+ http.StatusCreated,
+ http.StatusMovedPermanently,
+ http.StatusInternalServerError,
+ }
+
+ for _, code := range tests {
+ resp := &http.Response{StatusCode: code}
+
+ if err := CheckError(resp, code); err != nil {
+ t.Errorf("CheckError(%d) = %v", code, err)
+ }
+ }
+}
+
+func TestCheckErrorNotError(t *testing.T) {
+ tests := []struct {
+ code int
+ body string
+ msg string
+ request *http.Request
+ }{{
+ code: http.StatusBadRequest,
+ body: "",
+ msg: "unexpected status code 400 Bad Request",
+ }, {
+ code: http.StatusUnauthorized,
+ // Valid JSON, but not a structured error -- we should still print the body.
+ body: `{"details":"incorrect username or password"}`,
+ msg: `unexpected status code 401 Unauthorized: {"details":"incorrect username or password"}`,
+ }, {
+ code: http.StatusUnauthorized,
+ body: "Not JSON",
+ msg: "GET https://example.com/somepath?access_token=REDACTED&scope=foo&service=bar: unexpected status code 401 Unauthorized: Not JSON",
+ request: &http.Request{
+ Method: http.MethodGet,
+ URL: &url.URL{
+ Scheme: "https",
+ Host: "example.com",
+ Path: "somepath",
+ RawQuery: url.Values{
+ "scope": []string{"foo"},
+ "service": []string{"bar"},
+ "access_token": []string{"hunter2"},
+ }.Encode(),
+ },
+ },
+ }, {
+ code: http.StatusUnauthorized,
+ body: "",
+ msg: "HEAD https://example.com/somepath: unexpected status code 401 Unauthorized (HEAD responses have no body, use GET for details)",
+ request: &http.Request{
+ Method: http.MethodHead,
+ URL: &url.URL{
+ Scheme: "https",
+ Host: "example.com",
+ Path: "somepath",
+ },
+ },
+ }}
+
+ for _, test := range tests {
+ resp := &http.Response{
+ StatusCode: test.code,
+ Body: io.NopCloser(bytes.NewBufferString(test.body)),
+ Request: test.request,
+ }
+
+ err := CheckError(resp, http.StatusOK)
+ if err == nil {
+ t.Fatalf("CheckError(%d, %s) = nil, wanted error", test.code, test.body)
+ }
+ var terr *Error
+ if !errors.As(err, &terr) {
+ t.Fatalf("CheckError(%d, %s) = %v, wanted error type", test.code, test.body, err)
+ }
+
+ if terr.StatusCode != test.code {
+ t.Errorf("Incorrect status code, got %d, want %d", terr.StatusCode, test.code)
+ }
+
+ if terr.Error() != test.msg {
+ t.Errorf("Incorrect message, got %q, want %q", terr.Error(), test.msg)
+ }
+ }
+}
+
+func TestCheckErrorWithError(t *testing.T) {
+ tests := []struct {
+ name string
+ code int
+ errorBody string
+ msg string
+ }{{
+ name: "Invalid name error",
+ code: http.StatusBadRequest,
+ errorBody: `{"errors":[{"code":"NAME_INVALID","message":"a message for you"}],"StatusCode":400}`,
+ msg: "NAME_INVALID: a message for you",
+ }, {
+ name: "Only status code is provided",
+ code: http.StatusBadRequest,
+ errorBody: `{"StatusCode":400}`,
+ msg: "unexpected status code 400 Bad Request: {\"StatusCode\":400}",
+ }, {
+ name: "Multiple diagnostics",
+ code: http.StatusBadRequest,
+ errorBody: `{"errors":[{"code":"NAME_INVALID","message":"a message for you"}, {"code":"SIZE_INVALID","message":"another message for you", "detail": "with some details"}],"StatusCode":400,"Request":null}`,
+ msg: "multiple errors returned: NAME_INVALID: a message for you; SIZE_INVALID: another message for you; with some details",
+ }}
+
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ resp := &http.Response{
+ StatusCode: test.code,
+ Body: io.NopCloser(bytes.NewBuffer([]byte(test.errorBody))),
+ }
+
+ var terr *Error
+ if err := CheckError(resp, http.StatusOK); err == nil {
+ t.Errorf("CheckError(%d, %s) = nil, wanted error", test.code, test.errorBody)
+ } else if !errors.As(err, &terr) {
+ t.Errorf("CheckError(%d, %s) = %T, wanted *transport.Error", test.code, test.errorBody, err)
+ } else if diff := cmp.Diff(test.msg, err.Error()); diff != "" {
+ t.Errorf("CheckError(%d, %s).Error(); (-want +got) %s", test.code, test.errorBody, diff)
+ }
+ })
+ }
+}
+
+func TestBodyError(t *testing.T) {
+ expectedErr := errors.New("whoops")
+ resp := &http.Response{
+ StatusCode: http.StatusOK,
+ Body: &errReadCloser{expectedErr},
+ }
+ if err := CheckError(resp, http.StatusNotFound); err == nil {
+ t.Errorf("CheckError() = nil, wanted error %v", expectedErr)
+ } else if !errors.Is(err, expectedErr) {
+ t.Errorf("CheckError() = %v, wanted %v", err, expectedErr)
+ }
+}
+
+type errReadCloser struct {
+ err error
+}
+
+func (e *errReadCloser) Read(p []byte) (int, error) {
+ return 0, e.err
+}
+
+func (e *errReadCloser) Close() error {
+ return e.err
+}