189 lines
4.8 KiB
Go
189 lines
4.8 KiB
Go
|
|
package errs_test
|
||
|
|
|
||
|
|
import (
|
||
|
|
"errors"
|
||
|
|
"net/http"
|
||
|
|
"testing"
|
||
|
|
|
||
|
|
errs "gateway/internal/library/errors"
|
||
|
|
"gateway/internal/library/errors/code"
|
||
|
|
"google.golang.org/grpc/codes"
|
||
|
|
"google.golang.org/grpc/status"
|
||
|
|
)
|
||
|
|
|
||
|
|
var app = errs.For(code.Facade)
|
||
|
|
|
||
|
|
func TestNewValidation(t *testing.T) {
|
||
|
|
t.Parallel()
|
||
|
|
|
||
|
|
_, err := errs.New(code.Scope(100), code.InputInvalidFormat, 0, "bad scope")
|
||
|
|
if !errors.Is(err, errs.ErrInvalidCode) {
|
||
|
|
t.Fatalf("expected ErrInvalidCode, got %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
_, err = errs.New(code.Facade, code.Category(1000), 0, "bad category")
|
||
|
|
if !errors.Is(err, errs.ErrInvalidCode) {
|
||
|
|
t.Fatalf("expected ErrInvalidCode, got %v", err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestBuilderCode(t *testing.T) {
|
||
|
|
t.Parallel()
|
||
|
|
|
||
|
|
e := app.ResNotFound("user", "42")
|
||
|
|
if e.DisplayCode() != "10301000" {
|
||
|
|
t.Fatalf("DisplayCode = %s, want 10301000", e.DisplayCode())
|
||
|
|
}
|
||
|
|
if e.HTTPStatus() != http.StatusNotFound {
|
||
|
|
t.Fatalf("HTTPStatus = %d, want 404", e.HTTPStatus())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestWithCauseImmutable(t *testing.T) {
|
||
|
|
t.Parallel()
|
||
|
|
|
||
|
|
base := app.DBError("db fail")
|
||
|
|
wrapped := base.WithCause(errors.New("pq: connection refused"))
|
||
|
|
|
||
|
|
if base.Unwrap() != nil {
|
||
|
|
t.Fatal("WithCause must not mutate original error")
|
||
|
|
}
|
||
|
|
if wrapped.Unwrap() == nil {
|
||
|
|
t.Fatal("wrapped error should have cause")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestWithScope(t *testing.T) {
|
||
|
|
t.Parallel()
|
||
|
|
|
||
|
|
e := app.ResNotFound("x")
|
||
|
|
moved, err := e.WithScope(code.LocalAPI)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if moved.DisplayCode() != "11301000" {
|
||
|
|
t.Fatalf("DisplayCode = %s, want 11301000", moved.DisplayCode())
|
||
|
|
}
|
||
|
|
if e.DisplayCode() != "10301000" {
|
||
|
|
t.Fatal("WithScope must not mutate original error")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestIsUsesFullCode(t *testing.T) {
|
||
|
|
t.Parallel()
|
||
|
|
|
||
|
|
a := app.ResNotFound("a")
|
||
|
|
b := errs.For(code.LocalAPI).ResNotFound("b")
|
||
|
|
|
||
|
|
if errors.Is(a, b) {
|
||
|
|
t.Fatal("same category/detail but different scope must not match")
|
||
|
|
}
|
||
|
|
if !errors.Is(a, app.ResNotFound("c")) {
|
||
|
|
t.Fatal("same full code should match via errors.Is")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestFromCodeRoundTrip(t *testing.T) {
|
||
|
|
t.Parallel()
|
||
|
|
|
||
|
|
raw := uint32(10301123)
|
||
|
|
e, err := errs.FromCode(raw)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if e.Code() != raw {
|
||
|
|
t.Fatalf("Code = %d, want %d", e.Code(), raw)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestGRPCStatusBuiltin(t *testing.T) {
|
||
|
|
t.Parallel()
|
||
|
|
|
||
|
|
st := status.New(codes.NotFound, "missing")
|
||
|
|
e, err := errs.FromGRPCError(st.Err(), code.Facade)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if e.Category() != code.CatGRPC {
|
||
|
|
t.Fatalf("Category = %d, want %d", e.Category(), code.CatGRPC)
|
||
|
|
}
|
||
|
|
if e.Detail() != code.Detail(codes.NotFound) {
|
||
|
|
t.Fatalf("Detail = %d, want %d", e.Detail(), codes.NotFound)
|
||
|
|
}
|
||
|
|
if e.Scope() != code.Facade {
|
||
|
|
t.Fatalf("Scope = %d, want %d", e.Scope(), code.Facade)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestGRPCRoundTrip(t *testing.T) {
|
||
|
|
t.Parallel()
|
||
|
|
|
||
|
|
orig, err := app.InputInvalidFormat("email invalid").WithDetail(7)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
st := orig.GRPCStatus()
|
||
|
|
|
||
|
|
back, err := errs.FromGRPCError(st.Err())
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if back.Code() != orig.Code() {
|
||
|
|
t.Fatalf("Code = %d, want %d", back.Code(), orig.Code())
|
||
|
|
}
|
||
|
|
if back.Error() != orig.Error() {
|
||
|
|
t.Fatalf("msg = %q, want %q", back.Error(), orig.Error())
|
||
|
|
}
|
||
|
|
if back.GRPCCode() != codes.InvalidArgument {
|
||
|
|
t.Fatalf("GRPCCode = %s, want InvalidArgument", back.GRPCCode())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestCatGRPCHTTPStatus(t *testing.T) {
|
||
|
|
t.Parallel()
|
||
|
|
|
||
|
|
e := errs.MustNew(code.Facade, code.CatGRPC, code.Detail(codes.NotFound), "missing")
|
||
|
|
if e.HTTPStatus() != http.StatusNotFound {
|
||
|
|
t.Fatalf("HTTPStatus = %d, want 404", e.HTTPStatus())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestExtendedCategoriesHTTPStatus(t *testing.T) {
|
||
|
|
t.Parallel()
|
||
|
|
|
||
|
|
cases := []struct {
|
||
|
|
name string
|
||
|
|
got *errs.Error
|
||
|
|
status int
|
||
|
|
}{
|
||
|
|
{"InputMissingRequired", app.InputMissingRequired("name"), http.StatusBadRequest},
|
||
|
|
{"InputUnsupportedMedia", app.InputUnsupportedMedia("application/xml"), http.StatusUnsupportedMediaType},
|
||
|
|
{"InputPayloadTooLarge", app.InputPayloadTooLarge(), http.StatusRequestEntityTooLarge},
|
||
|
|
{"DBUnavailable", app.DBUnavailable(), http.StatusServiceUnavailable},
|
||
|
|
{"ResPreconditionFailed", app.ResPreconditionFailed("etag mismatch"), http.StatusPreconditionFailed},
|
||
|
|
{"ResLocked", app.ResLocked(), http.StatusLocked},
|
||
|
|
{"AuthMethodNotAllowed", app.AuthMethodNotAllowed("POST"), http.StatusMethodNotAllowed},
|
||
|
|
{"SysNotImplemented", app.SysNotImplemented(), http.StatusNotImplemented},
|
||
|
|
{"SysClientTimeout", app.SysClientTimeout(), http.StatusRequestTimeout},
|
||
|
|
{"SvcRateLimited", app.SvcRateLimited("payment provider"), http.StatusTooManyRequests},
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, tc := range cases {
|
||
|
|
t.Run(tc.name, func(t *testing.T) {
|
||
|
|
t.Parallel()
|
||
|
|
if tc.got.HTTPStatus() != tc.status {
|
||
|
|
t.Fatalf("HTTPStatus() = %d, want %d (code %s)", tc.got.HTTPStatus(), tc.status, tc.got.DisplayCode())
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestBuilderCustomDetail(t *testing.T) {
|
||
|
|
t.Parallel()
|
||
|
|
|
||
|
|
e := app.Code(code.SysTimeout, 42, "downstream timeout")
|
||
|
|
if e.Detail() != 42 {
|
||
|
|
t.Fatalf("Detail = %d, want 42", e.Detail())
|
||
|
|
}
|
||
|
|
}
|