package mxgateway import ( "context" "errors" "fmt" "strings" "testing" pb "gitea.dohertylan.com/dohertj2/mxaccessgw/clients/go/internal/generated" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) // #198 — the gateway answers a bad key with Unauthenticated and a missing scope // with PermissionDenied; callers must be able to tell them apart with errors.Is. func TestGatewayErrorMatchesAuthSentinelsByStatusCode(t *testing.T) { cases := []struct { name string err error unauthenticated bool permissionDenied bool }{ {"unauthenticated", status.Error(codes.Unauthenticated, "bad key"), true, false}, {"permission denied", status.Error(codes.PermissionDenied, "scope required"), false, true}, {"unavailable", status.Error(codes.Unavailable, "down"), false, false}, {"non-grpc", errors.New("plain"), false, false}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { err := fmt.Errorf("outer: %w", &GatewayError{Op: "invoke", Err: tc.err}) if got := errors.Is(err, ErrUnauthenticated); got != tc.unauthenticated { t.Fatalf("errors.Is(ErrUnauthenticated) = %v, want %v", got, tc.unauthenticated) } if got := errors.Is(err, ErrPermissionDenied); got != tc.permissionDenied { t.Fatalf("errors.Is(ErrPermissionDenied) = %v, want %v", got, tc.permissionDenied) } }) } } func TestGatewayErrorCode(t *testing.T) { if got := (&GatewayError{Err: status.Error(codes.PermissionDenied, "x")}).Code(); got != codes.PermissionDenied { t.Fatalf("Code() = %v", got) } if got := (*GatewayError)(nil).Code(); got != codes.OK { t.Fatalf("nil Code() = %v", got) } } func TestAuthSentinelsSurviveTheWireAndRedaction(t *testing.T) { for _, tc := range []struct { code codes.Code sentinel error other error }{ {codes.Unauthenticated, ErrUnauthenticated, ErrPermissionDenied}, {codes.PermissionDenied, ErrPermissionDenied, ErrUnauthenticated}, } { t.Run(tc.code.String(), func(t *testing.T) { fake := &fakeGatewayWithAlarms{acknowledgeError: status.Error(tc.code, "denied pw-secret")} client, cleanup := newBufconnClientWithAlarms(t, fake) defer cleanup() _, err := client.AcknowledgeAlarm(context.Background(), &pb.AcknowledgeAlarmRequest{ AlarmFullReference: "Tank01.Level.HiHi", OperatorUser: "alice", OperatorPassword: "pw-secret", }) if err == nil { t.Fatal("AcknowledgeAlarm() returned no error") } if !errors.Is(err, tc.sentinel) { t.Fatalf("errors.Is(%v) = false for %v", tc.sentinel, err) } if errors.Is(err, tc.other) { t.Fatalf("errors.Is(%v) = true for %v", tc.other, err) } if strings.Contains(err.Error(), "pw-secret") { t.Fatalf("error leaked the operator password: %v", err) } }) } }