...

Source file src/github.com/googleapis/gax-go/v2/apierror/apierror_test.go

Documentation: github.com/googleapis/gax-go/v2/apierror

     1  // Copyright 2021, Google Inc.
     2  // All rights reserved.
     3  //
     4  // Redistribution and use in source and binary forms, with or without
     5  // modification, are permitted provided that the following conditions are
     6  // met:
     7  //
     8  //     * Redistributions of source code must retain the above copyright
     9  // notice, this list of conditions and the following disclaimer.
    10  //     * Redistributions in binary form must reproduce the above
    11  // copyright notice, this list of conditions and the following disclaimer
    12  // in the documentation and/or other materials provided with the
    13  // distribution.
    14  //     * Neither the name of Google Inc. nor the names of its
    15  // contributors may be used to endorse or promote products derived from
    16  // this software without specific prior written permission.
    17  //
    18  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    19  // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    20  // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    21  // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    22  // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    23  // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    24  // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    25  // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    26  // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    27  // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    28  // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    29  
    30  package apierror
    31  
    32  import (
    33  	"context"
    34  	"errors"
    35  	"flag"
    36  	"io/ioutil"
    37  	"path/filepath"
    38  	"testing"
    39  
    40  	"github.com/google/go-cmp/cmp"
    41  	"github.com/google/go-cmp/cmp/cmpopts"
    42  	jsonerror "github.com/googleapis/gax-go/v2/apierror/internal/proto"
    43  	"google.golang.org/api/googleapi"
    44  	"google.golang.org/genproto/googleapis/rpc/errdetails"
    45  	"google.golang.org/grpc/codes"
    46  	"google.golang.org/grpc/status"
    47  	"google.golang.org/protobuf/encoding/protojson"
    48  	"google.golang.org/protobuf/proto"
    49  	"google.golang.org/protobuf/testing/protocmp"
    50  	"google.golang.org/protobuf/types/descriptorpb"
    51  	"google.golang.org/protobuf/types/known/anypb"
    52  	"google.golang.org/protobuf/types/known/durationpb"
    53  )
    54  
    55  var update = flag.Bool("update", false, "update golden files")
    56  
    57  func TestDetails(t *testing.T) {
    58  	qf := &errdetails.QuotaFailure{
    59  		Violations: []*errdetails.QuotaFailure_Violation{{Subject: "Foo", Description: "Bar"}},
    60  	}
    61  	qS, _ := status.New(codes.ResourceExhausted, "test").WithDetails(qf)
    62  	apierr := &APIError{
    63  		err:     qS.Err(),
    64  		status:  qS,
    65  		details: ErrDetails{QuotaFailure: qf},
    66  	}
    67  	got := apierr.Details()
    68  	want := ErrDetails{QuotaFailure: qf}
    69  	if diff := cmp.Diff(got, want, cmp.Comparer(proto.Equal)); diff != "" {
    70  		t.Errorf("got(-), want(+):\n%s", diff)
    71  	}
    72  }
    73  
    74  func TestDetails_ExtractProtoMessage(t *testing.T) {
    75  
    76  	customError := &jsonerror.CustomError{
    77  		Code:         jsonerror.CustomError_UNIVERSE_WAS_DESTROYED,
    78  		Entity:       "some entity",
    79  		ErrorMessage: "custom error message",
    80  	}
    81  
    82  	testCases := []struct {
    83  		description string
    84  		src         *status.Status
    85  		extract     proto.Message
    86  		want        interface{}
    87  		wantErr     error
    88  	}{
    89  		{
    90  			description: "no details",
    91  			src:         status.New(codes.Unimplemented, "unimp"),
    92  			extract:     &jsonerror.CustomError{},
    93  			wantErr:     ErrMessageNotFound,
    94  		},
    95  		{
    96  			description: "nil argument",
    97  			src: func() *status.Status {
    98  				s, _ := status.New(codes.Unauthenticated, "who are you").WithDetails(
    99  					&descriptorpb.DescriptorProto{},
   100  				)
   101  				return s
   102  			}(),
   103  			wantErr: ErrMessageNotFound,
   104  		},
   105  		{
   106  			description: "custom error success",
   107  			src: func() *status.Status {
   108  				s, _ := status.New(codes.Unknown, "unknown error").WithDetails(
   109  					customError,
   110  				)
   111  				return s
   112  			}(),
   113  			extract: &jsonerror.CustomError{},
   114  			want:    customError,
   115  		},
   116  	}
   117  	for _, tc := range testCases {
   118  
   119  		apiErr, ok := FromError(tc.src.Err())
   120  		if !ok {
   121  			t.Errorf("%s: FromError failure", tc.description)
   122  		}
   123  		val := tc.extract
   124  		gotErr := apiErr.Details().ExtractProtoMessage(val)
   125  		if tc.wantErr != nil {
   126  			if !errors.Is(gotErr, tc.wantErr) {
   127  				t.Errorf("%s: got error %v, wanted error %v", tc.description, gotErr, tc.wantErr)
   128  			}
   129  		} else {
   130  			if gotErr != nil {
   131  				t.Errorf("%s: got error %v", tc.description, gotErr)
   132  			}
   133  			if diff := cmp.Diff(val, tc.want, protocmp.Transform()); diff != "" {
   134  				t.Errorf("%s: got(-), want(+):\n%s", tc.description, diff)
   135  			}
   136  		}
   137  	}
   138  }
   139  func TestUnwrap(t *testing.T) {
   140  	pf := &errdetails.PreconditionFailure{
   141  		Violations: []*errdetails.PreconditionFailure_Violation{{Type: "Foo", Subject: "Bar", Description: "desc"}},
   142  	}
   143  	pS, _ := status.New(codes.FailedPrecondition, "test").WithDetails(pf)
   144  	apierr := &APIError{
   145  		err:     pS.Err(),
   146  		status:  pS,
   147  		details: ErrDetails{PreconditionFailure: pf},
   148  	}
   149  	got := apierr.Unwrap()
   150  	want := pS.Err()
   151  	if diff := cmp.Diff(got, want, cmpopts.EquateErrors()); diff != "" {
   152  		t.Errorf("got(-), want(+):\n%s", diff)
   153  	}
   154  }
   155  func TestError(t *testing.T) {
   156  	ei := &errdetails.ErrorInfo{
   157  		Reason:   "Foo",
   158  		Domain:   "Bar",
   159  		Metadata: map[string]string{"type": "test"},
   160  	}
   161  	eS, _ := status.New(codes.Unauthenticated, "ei").WithDetails(ei)
   162  
   163  	br := &errdetails.BadRequest{FieldViolations: []*errdetails.BadRequest_FieldViolation{{
   164  		Field:       "Foo",
   165  		Description: "Bar",
   166  	}},
   167  	}
   168  	bS, _ := status.New(codes.InvalidArgument, "br").WithDetails(br)
   169  
   170  	qf := &errdetails.QuotaFailure{
   171  		Violations: []*errdetails.QuotaFailure_Violation{{Subject: "Foo", Description: "Bar"}},
   172  	}
   173  	pf := &errdetails.PreconditionFailure{
   174  		Violations: []*errdetails.PreconditionFailure_Violation{{Type: "Foo", Subject: "Bar", Description: "desc"}},
   175  	}
   176  
   177  	ri := &errdetails.RetryInfo{
   178  		RetryDelay: &durationpb.Duration{Seconds: 10, Nanos: 10},
   179  	}
   180  	rq := &errdetails.RequestInfo{
   181  		RequestId:   "Foo",
   182  		ServingData: "Bar",
   183  	}
   184  	rqS, _ := status.New(codes.Canceled, "Request cancelled by client").WithDetails(rq, ri, pf, br, qf)
   185  
   186  	rs := &errdetails.ResourceInfo{
   187  		ResourceType: "Foo",
   188  		ResourceName: "Bar",
   189  		Owner:        "Client",
   190  		Description:  "Directory not Found",
   191  	}
   192  	rS, _ := status.New(codes.NotFound, "rs").WithDetails(rs)
   193  
   194  	deb := &errdetails.DebugInfo{
   195  		StackEntries: []string{"Foo", "Bar"},
   196  		Detail:       "Stack",
   197  	}
   198  	dS, _ := status.New(codes.DataLoss, "Here is the debug info").WithDetails(deb)
   199  
   200  	hp := &errdetails.Help{
   201  		Links: []*errdetails.Help_Link{{Description: "Foo", Url: "Bar"}},
   202  	}
   203  	hS, _ := status.New(codes.Unimplemented, "Help Info").WithDetails(hp)
   204  
   205  	lo := &errdetails.LocalizedMessage{
   206  		Locale:  "Foo",
   207  		Message: "Bar",
   208  	}
   209  	lS, _ := status.New(codes.Unknown, "Localized Message").WithDetails(lo)
   210  
   211  	var uu []interface{}
   212  	uu = append(uu, "unknown detail 1")
   213  	uS := status.New(codes.Unknown, "Unknown")
   214  
   215  	httpErrInfo := &errdetails.ErrorInfo{Reason: "just because", Domain: "tests"}
   216  	any, err := anypb.New(httpErrInfo)
   217  	if err != nil {
   218  		t.Fatal(err)
   219  	}
   220  	e := &jsonerror.Error{Error: &jsonerror.Error_Status{Details: []*anypb.Any{any}}}
   221  	data, err := protojson.Marshal(e)
   222  	if err != nil {
   223  		t.Fatal(err)
   224  	}
   225  	hae := &googleapi.Error{
   226  		Message: "just because",
   227  		Body:    string(data),
   228  	}
   229  	haeS := status.New(codes.Unknown, "just because")
   230  
   231  	tests := []struct {
   232  		apierr *APIError
   233  		name   string
   234  	}{
   235  		{&APIError{err: eS.Err(), status: eS, details: ErrDetails{ErrorInfo: ei}}, "error_info"},
   236  		{&APIError{err: bS.Err(), status: bS, details: ErrDetails{BadRequest: br}}, "bad_request"},
   237  		{&APIError{err: rqS.Err(), status: rqS, details: ErrDetails{RequestInfo: rq, RetryInfo: ri,
   238  			PreconditionFailure: pf, QuotaFailure: qf, BadRequest: br}}, "multiple_info"},
   239  		{&APIError{err: bS.Err(), status: rS, details: ErrDetails{ResourceInfo: rs}}, "resource_info"},
   240  		{&APIError{err: bS.Err(), status: dS, details: ErrDetails{DebugInfo: deb}}, "debug_info"},
   241  		{&APIError{err: bS.Err(), status: hS, details: ErrDetails{Help: hp}}, "help"},
   242  		{&APIError{err: bS.Err(), status: lS, details: ErrDetails{LocalizedMessage: lo}}, "localized_message"},
   243  		{&APIError{err: bS.Err(), status: uS, details: ErrDetails{Unknown: uu}}, "unknown"},
   244  		{&APIError{err: bS.Err(), status: bS, details: ErrDetails{}}, "empty"},
   245  		{&APIError{err: hae, httpErr: hae, status: haeS, details: ErrDetails{ErrorInfo: httpErrInfo}}, "http_err"},
   246  	}
   247  	for _, tc := range tests {
   248  		t.Helper()
   249  		got := tc.apierr.Error()
   250  		want, err := golden(tc.name, got)
   251  		if err != nil {
   252  			t.Fatal(err)
   253  		}
   254  		if diff := cmp.Diff(got, want); diff != "" {
   255  			t.Errorf("got(-), want(+),: \n%s", diff)
   256  		}
   257  	}
   258  
   259  }
   260  
   261  func TestGRPCStatus(t *testing.T) {
   262  	qf := &errdetails.QuotaFailure{
   263  		Violations: []*errdetails.QuotaFailure_Violation{{Subject: "Foo", Description: "Bar"}},
   264  	}
   265  	want, _ := status.New(codes.ResourceExhausted, "test").WithDetails(qf)
   266  	apierr := &APIError{
   267  		err:     want.Err(),
   268  		status:  want,
   269  		details: ErrDetails{QuotaFailure: qf},
   270  	}
   271  	got := apierr.GRPCStatus()
   272  	if diff := cmp.Diff(got, want, cmp.Comparer(proto.Equal), cmp.AllowUnexported(status.Status{})); diff != "" {
   273  		t.Errorf("got(-), want(+),: \n%s", diff)
   274  	}
   275  }
   276  
   277  func TestReason(t *testing.T) {
   278  	tests := []struct {
   279  		ei *errdetails.ErrorInfo
   280  	}{
   281  		{&errdetails.ErrorInfo{Reason: "Foo"}},
   282  		{&errdetails.ErrorInfo{}},
   283  	}
   284  	for _, tc := range tests {
   285  		apierr := toAPIError(tc.ei)
   286  		if diff := cmp.Diff(apierr.Reason(), tc.ei.GetReason()); diff != "" {
   287  			t.Errorf("got(-), want(+),: \n%s", diff)
   288  		}
   289  	}
   290  }
   291  func TestDomain(t *testing.T) {
   292  	tests := []struct {
   293  		ei *errdetails.ErrorInfo
   294  	}{
   295  		{&errdetails.ErrorInfo{Domain: "Bar"}},
   296  		{&errdetails.ErrorInfo{}},
   297  	}
   298  	for _, tc := range tests {
   299  		apierr := toAPIError(tc.ei)
   300  		if diff := cmp.Diff(apierr.Domain(), tc.ei.GetDomain()); diff != "" {
   301  			t.Errorf("got(-), want(+),: \n%s", diff)
   302  		}
   303  	}
   304  }
   305  func TestMetadata(t *testing.T) {
   306  	tests := []struct {
   307  		ei *errdetails.ErrorInfo
   308  	}{
   309  		{&errdetails.ErrorInfo{Metadata: map[string]string{"type": "test"}}},
   310  		{&errdetails.ErrorInfo{}},
   311  	}
   312  	for _, tc := range tests {
   313  		apierr := toAPIError(tc.ei)
   314  		if diff := cmp.Diff(apierr.Metadata(), tc.ei.GetMetadata()); diff != "" {
   315  			t.Errorf("got(-), want(+),: \n%s", diff)
   316  		}
   317  	}
   318  }
   319  
   320  func TestFromError(t *testing.T) {
   321  	ei := &errdetails.ErrorInfo{
   322  		Reason:   "Foo",
   323  		Domain:   "Bar",
   324  		Metadata: map[string]string{"type": "test"},
   325  	}
   326  	eS, _ := status.New(codes.Unauthenticated, "ei").WithDetails(ei)
   327  
   328  	br := &errdetails.BadRequest{FieldViolations: []*errdetails.BadRequest_FieldViolation{{
   329  		Field:       "Foo",
   330  		Description: "Bar",
   331  	}},
   332  	}
   333  	bS, _ := status.New(codes.InvalidArgument, "br").WithDetails(br)
   334  
   335  	qf := &errdetails.QuotaFailure{
   336  		Violations: []*errdetails.QuotaFailure_Violation{{Subject: "Foo", Description: "Bar"}},
   337  	}
   338  	qS, _ := status.New(codes.ResourceExhausted, "qf").WithDetails(qf, br)
   339  
   340  	pf := &errdetails.PreconditionFailure{
   341  		Violations: []*errdetails.PreconditionFailure_Violation{{Type: "Foo", Subject: "Bar", Description: "desc"}},
   342  	}
   343  	pS, _ := status.New(codes.FailedPrecondition, "pf").WithDetails(pf)
   344  
   345  	ri := &errdetails.RetryInfo{
   346  		RetryDelay: &durationpb.Duration{Seconds: 10, Nanos: 10},
   347  	}
   348  	riS, _ := status.New(codes.Unavailable, "foo").WithDetails(ri)
   349  
   350  	rs := &errdetails.ResourceInfo{
   351  		ResourceType: "Foo",
   352  		ResourceName: "Bar",
   353  		Owner:        "Client",
   354  		Description:  "Directory not Found",
   355  	}
   356  	rS, _ := status.New(codes.NotFound, "rs").WithDetails(rs)
   357  
   358  	rq := &errdetails.RequestInfo{
   359  		RequestId:   "Foo",
   360  		ServingData: "Bar",
   361  	}
   362  	rqS, _ := status.New(codes.Canceled, "Request cancelled by client").WithDetails(rq)
   363  
   364  	deb := &errdetails.DebugInfo{
   365  		StackEntries: []string{"Foo", "Bar"},
   366  		Detail:       "Stack",
   367  	}
   368  	dS, _ := status.New(codes.DataLoss, "Here is the debug info").WithDetails(deb)
   369  
   370  	hp := &errdetails.Help{
   371  		Links: []*errdetails.Help_Link{{Description: "Foo", Url: "Bar"}},
   372  	}
   373  	hS, _ := status.New(codes.Unimplemented, "Help Info").WithDetails(hp)
   374  
   375  	lo := &errdetails.LocalizedMessage{
   376  		Locale:  "Foo",
   377  		Message: "Bar",
   378  	}
   379  	lS, _ := status.New(codes.Unknown, "Localized Message").WithDetails(lo)
   380  
   381  	msg := &descriptorpb.DescriptorProto{
   382  		Name: proto.String("Foo"),
   383  	}
   384  	u := []interface{}{msg}
   385  	uS, _ := status.New(codes.Unknown, "test").WithDetails(msg)
   386  
   387  	httpErrInfo := &errdetails.ErrorInfo{Reason: "just because", Domain: "tests"}
   388  	any, err := anypb.New(httpErrInfo)
   389  	if err != nil {
   390  		t.Fatal(err)
   391  	}
   392  	e := &jsonerror.Error{Error: &jsonerror.Error_Status{Details: []*anypb.Any{any}}}
   393  	data, err := protojson.Marshal(e)
   394  	if err != nil {
   395  		t.Fatal(err)
   396  	}
   397  	hae := &googleapi.Error{
   398  		Message: "just because",
   399  		Body:    string(data),
   400  	}
   401  	haeS := status.New(codes.Unknown, "just because")
   402  
   403  	tests := []struct {
   404  		apierr *APIError
   405  		b      bool
   406  	}{
   407  		{&APIError{err: eS.Err(), status: eS, details: ErrDetails{ErrorInfo: ei}}, true},
   408  		{&APIError{err: bS.Err(), status: bS, details: ErrDetails{BadRequest: br}}, true},
   409  		{&APIError{err: qS.Err(), status: qS, details: ErrDetails{QuotaFailure: qf, BadRequest: br}}, true},
   410  		{&APIError{err: pS.Err(), status: pS, details: ErrDetails{PreconditionFailure: pf}}, true},
   411  		{&APIError{err: riS.Err(), status: riS, details: ErrDetails{RetryInfo: ri}}, true},
   412  		{&APIError{err: rS.Err(), status: rS, details: ErrDetails{ResourceInfo: rs}}, true},
   413  		{&APIError{err: rqS.Err(), status: rqS, details: ErrDetails{RequestInfo: rq}}, true},
   414  		{&APIError{err: dS.Err(), status: dS, details: ErrDetails{DebugInfo: deb}}, true},
   415  		{&APIError{err: hS.Err(), status: hS, details: ErrDetails{Help: hp}}, true},
   416  		{&APIError{err: lS.Err(), status: lS, details: ErrDetails{LocalizedMessage: lo}}, true},
   417  		{&APIError{err: uS.Err(), status: uS, details: ErrDetails{Unknown: u}}, true},
   418  		{&APIError{err: hae, httpErr: hae, status: haeS, details: ErrDetails{ErrorInfo: httpErrInfo}}, true},
   419  		{&APIError{err: errors.New("standard error")}, false},
   420  	}
   421  
   422  	for _, tc := range tests {
   423  		got, apiB := FromError(tc.apierr.err)
   424  		if tc.b != apiB {
   425  			t.Errorf("FromError(%s): got %v, want %v", tc.apierr.err, apiB, tc.b)
   426  		}
   427  		if tc.b {
   428  			if diff := cmp.Diff(got.details, tc.apierr.details, cmp.Comparer(proto.Equal)); diff != "" {
   429  				t.Errorf("FromError(%s): got(-), want(+),: \n%s", tc.apierr.err, diff)
   430  			}
   431  			if diff := cmp.Diff(got.status, tc.apierr.status, cmp.Comparer(proto.Equal), cmp.AllowUnexported(status.Status{})); diff != "" {
   432  				t.Errorf("FromError(%s): got(-), want(+),: \n%s", tc.apierr.err, diff)
   433  			}
   434  			if diff := cmp.Diff(got.err, tc.apierr.err, cmpopts.EquateErrors()); diff != "" {
   435  				t.Errorf("FromError(%s): got(-), want(+),: \n%s", tc.apierr.err, diff)
   436  			}
   437  		}
   438  	}
   439  	if err, _ := FromError(nil); err != nil {
   440  		t.Errorf("got %s, want nil", err)
   441  	}
   442  
   443  	if c, _ := FromError(context.DeadlineExceeded); c != nil {
   444  		t.Errorf("got %s, want nil", c)
   445  	}
   446  }
   447  
   448  func TestParseError(t *testing.T) {
   449  	httpErrInfo := &errdetails.ErrorInfo{Reason: "just because", Domain: "tests"}
   450  	any, err := anypb.New(httpErrInfo)
   451  	if err != nil {
   452  		t.Fatal(err)
   453  	}
   454  	e := &jsonerror.Error{Error: &jsonerror.Error_Status{Details: []*anypb.Any{any}}}
   455  	data, err := protojson.Marshal(e)
   456  	if err != nil {
   457  		t.Fatal(err)
   458  	}
   459  	hae := &googleapi.Error{
   460  		Message: "just because",
   461  		Body:    string(data),
   462  	}
   463  	haeS := status.New(codes.Unknown, "just because")
   464  
   465  	se := errors.New("standard error")
   466  
   467  	tests := []struct {
   468  		source error
   469  		apierr *APIError
   470  		b      bool
   471  	}{
   472  		{hae, &APIError{httpErr: hae, status: haeS, details: ErrDetails{ErrorInfo: httpErrInfo}}, true},
   473  		{se, &APIError{err: se}, false},
   474  	}
   475  
   476  	for _, tc := range tests {
   477  		// ParseError with wrap = true is covered by TestFromError, above.
   478  		got, apiB := ParseError(tc.source, false)
   479  		if tc.b != apiB {
   480  			t.Errorf("ParseError(%s, false): got %v, want %v", tc.apierr, apiB, tc.b)
   481  		}
   482  		if tc.b {
   483  			if diff := cmp.Diff(got.details, tc.apierr.details, cmp.Comparer(proto.Equal)); diff != "" {
   484  				t.Errorf("got(-), want(+),: \n%s", diff)
   485  			}
   486  			if diff := cmp.Diff(got.status, tc.apierr.status, cmp.Comparer(proto.Equal), cmp.AllowUnexported(status.Status{})); diff != "" {
   487  				t.Errorf("got(-), want(+),: \n%s", diff)
   488  			}
   489  			if got.err != nil {
   490  				t.Errorf("got %s, want nil", got.err)
   491  			}
   492  		}
   493  	}
   494  	if err, _ := ParseError(nil, false); err != nil {
   495  		t.Errorf("got %s, want nil", err)
   496  	}
   497  
   498  	if c, _ := ParseError(context.DeadlineExceeded, false); c != nil {
   499  		t.Errorf("got %s, want nil", c)
   500  	}
   501  }
   502  
   503  func golden(name, got string) (string, error) {
   504  	g := filepath.Join("testdata", name+".golden")
   505  	if *update {
   506  		if err := ioutil.WriteFile(g, []byte(got), 0644); err != nil {
   507  			return "", err
   508  		}
   509  	}
   510  	want, err := ioutil.ReadFile(g)
   511  	return string(want), err
   512  }
   513  
   514  func toAPIError(e *errdetails.ErrorInfo) *APIError {
   515  	st, _ := status.New(codes.Unavailable, "test").WithDetails(e)
   516  	return &APIError{
   517  		err:     st.Err(),
   518  		status:  st,
   519  		details: ErrDetails{ErrorInfo: e},
   520  	}
   521  }
   522  
   523  func TestHTTPCode(t *testing.T) {
   524  	tests := []struct {
   525  		name   string
   526  		apierr *APIError
   527  		want   int
   528  	}{
   529  		{
   530  			name:   "basic http error",
   531  			apierr: &APIError{httpErr: &googleapi.Error{Code: 418}},
   532  			want:   418,
   533  		},
   534  		{
   535  			name:   "http error, with unknown status",
   536  			apierr: &APIError{httpErr: &googleapi.Error{Code: 418}, status: status.New(codes.Unknown, "???")},
   537  			want:   418,
   538  		},
   539  		{
   540  			name:   "gRPC error",
   541  			apierr: &APIError{status: status.New(codes.DataLoss, "where did it go?")},
   542  			want:   -1,
   543  		},
   544  	}
   545  
   546  	for _, tt := range tests {
   547  		t.Run(tt.name, func(t *testing.T) {
   548  			if got := tt.apierr.HTTPCode(); got != tt.want {
   549  				t.Errorf("HTTPCode() = %v, want %v", got, tt.want)
   550  			}
   551  		})
   552  	}
   553  }
   554  

View as plain text