/* * * Copyright 2017 gRPC authors. * * 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 status import ( "errors" "fmt" "reflect" "testing" "github.com/golang/protobuf/proto" "github.com/golang/protobuf/ptypes" apb "github.com/golang/protobuf/ptypes/any" dpb "github.com/golang/protobuf/ptypes/duration" cpb "google.golang.org/genproto/googleapis/rpc/code" epb "google.golang.org/genproto/googleapis/rpc/errdetails" spb "google.golang.org/genproto/googleapis/rpc/status" "google.golang.org/grpc/codes" ) func TestErrorsWithSameParameters(t *testing.T) { const description = "some description" e1 := Errorf(codes.AlreadyExists, description) e2 := Errorf(codes.AlreadyExists, description) if e1 == e2 || !reflect.DeepEqual(e1, e2) { t.Fatalf("Errors should be equivalent but unique - e1: %v, %v e2: %p, %v", e1.(*statusError), e1, e2.(*statusError), e2) } } func TestFromToProto(t *testing.T) { s := &spb.Status{ Code: int32(codes.Internal), Message: "test test test", Details: []*apb.Any{{TypeUrl: "foo", Value: []byte{3, 2, 1}}}, } err := FromProto(s) if got := err.Proto(); !proto.Equal(s, got) { t.Fatalf("Expected errors to be identical - s: %v got: %v", s, got) } } func TestFromNilProto(t *testing.T) { tests := []*Status{nil, FromProto(nil)} for _, s := range tests { if c := s.Code(); c != codes.OK { t.Errorf("s: %v - Expected s.Code() = OK; got %v", s, c) } if m := s.Message(); m != "" { t.Errorf("s: %v - Expected s.Message() = \"\"; got %q", s, m) } if p := s.Proto(); p != nil { t.Errorf("s: %v - Expected s.Proto() = nil; got %q", s, p) } if e := s.Err(); e != nil { t.Errorf("s: %v - Expected s.Err() = nil; got %v", s, e) } } } func TestError(t *testing.T) { err := Error(codes.Internal, "test description") if got, want := err.Error(), "rpc error: code = Internal desc = test description"; got != want { t.Fatalf("err.Error() = %q; want %q", got, want) } s, _ := FromError(err) if got, want := s.Code(), codes.Internal; got != want { t.Fatalf("err.Code() = %s; want %s", got, want) } if got, want := s.Message(), "test description"; got != want { t.Fatalf("err.Message() = %s; want %s", got, want) } } func TestErrorOK(t *testing.T) { err := Error(codes.OK, "foo") if err != nil { t.Fatalf("Error(codes.OK, _) = %p; want nil", err.(*statusError)) } } func TestErrorProtoOK(t *testing.T) { s := &spb.Status{Code: int32(codes.OK)} if got := ErrorProto(s); got != nil { t.Fatalf("ErrorProto(%v) = %v; want nil", s, got) } } func TestFromError(t *testing.T) { code, message := codes.Internal, "test description" err := Error(code, message) s, ok := FromError(err) if !ok || s.Code() != code || s.Message() != message || s.Err() == nil { t.Fatalf("FromError(%v) = %v, %v; want , true", err, s, ok, code, message) } } func TestFromErrorOK(t *testing.T) { code, message := codes.OK, "" s, ok := FromError(nil) if !ok || s.Code() != code || s.Message() != message || s.Err() != nil { t.Fatalf("FromError(nil) = %v, %v; want , true", s, ok, code, message) } } func TestStatus_ErrorDetails(t *testing.T) { tests := []struct { code codes.Code details []proto.Message }{ { code: codes.NotFound, details: nil, }, { code: codes.NotFound, details: []proto.Message{ &epb.ResourceInfo{ ResourceType: "book", ResourceName: "projects/1234/books/5678", Owner: "User", }, }, }, { code: codes.Internal, details: []proto.Message{ &epb.DebugInfo{ StackEntries: []string{ "first stack", "second stack", }, }, }, }, { code: codes.Unavailable, details: []proto.Message{ &epb.RetryInfo{ RetryDelay: &dpb.Duration{Seconds: 60}, }, &epb.ResourceInfo{ ResourceType: "book", ResourceName: "projects/1234/books/5678", Owner: "User", }, }, }, } for _, tc := range tests { s, err := New(tc.code, "").WithDetails(tc.details...) if err != nil { t.Fatalf("(%v).WithDetails(%+v) failed: %v", str(s), tc.details, err) } details := s.Details() for i := range details { if !proto.Equal(details[i].(proto.Message), tc.details[i]) { t.Fatalf("(%v).Details()[%d] = %+v, want %+v", str(s), i, details[i], tc.details[i]) } } } } func TestStatus_WithDetails_Fail(t *testing.T) { tests := []*Status{ nil, FromProto(nil), New(codes.OK, ""), } for _, s := range tests { if s, err := s.WithDetails(); err == nil || s != nil { t.Fatalf("(%v).WithDetails(%+v) = %v, %v; want nil, non-nil", str(s), []proto.Message{}, s, err) } } } func TestStatus_ErrorDetails_Fail(t *testing.T) { tests := []struct { s *Status i []interface{} }{ { nil, nil, }, { FromProto(nil), nil, }, { New(codes.OK, ""), []interface{}{}, }, { FromProto(&spb.Status{ Code: int32(cpb.Code_CANCELLED), Details: []*apb.Any{ { TypeUrl: "", Value: []byte{}, }, mustMarshalAny(&epb.ResourceInfo{ ResourceType: "book", ResourceName: "projects/1234/books/5678", Owner: "User", }), }, }), []interface{}{ errors.New(`message type url "" is invalid`), &epb.ResourceInfo{ ResourceType: "book", ResourceName: "projects/1234/books/5678", Owner: "User", }, }, }, } for _, tc := range tests { got := tc.s.Details() if !reflect.DeepEqual(got, tc.i) { t.Errorf("(%v).Details() = %+v, want %+v", str(tc.s), got, tc.i) } } } func str(s *Status) string { if s == nil { return "nil" } if s.s == nil { return "" } return fmt.Sprintf("", codes.Code(s.s.GetCode()), s.s.GetMessage(), s.s.GetDetails()) } // mustMarshalAny converts a protobuf message to an any. func mustMarshalAny(msg proto.Message) *apb.Any { any, err := ptypes.MarshalAny(msg) if err != nil { panic(fmt.Sprintf("ptypes.MarshalAny(%+v) failed: %v", msg, err)) } return any }