package apierrors_test import ( "encoding/json" "errors" "fmt" "math" "sync" "testing" apierrors "git.yuntuo.hk/k2safe/go-omni/errors" ) func TestCatalogIsolationDefaultsAndCauses(t *testing.T) { catalog := apierrors.Catalog{DefaultStatus: 200, Definitions: map[string]apierrors.Definition{ "FIRST": {Code: 400002, Message: "first"}, "SECOND": {Code: 400002, Message: "second"}, "DENIED": {Status: 403, Code: "denied", Message: "denied"}, }} r, err := apierrors.NewRegistry(catalog) if err != nil { t.Fatal(err) } catalog.Definitions["FIRST"] = apierrors.Definition{Message: "changed"} r.Catalog().Definitions["FIRST"] = apierrors.Definition{Message: "also changed"} cause := errors.New("private secret") var wg sync.WaitGroup for i := 0; i < 40; i++ { wg.Add(1) go func() { defer wg.Done() ref := apierrors.ByCode("FIRST").WithCause(cause) e := r.Resolve(fmt.Errorf("wrapped: %w", ref)) if e.Status != 200 || e.BusinessCode != int64(400002) || e.Message != "first" || !errors.Is(e, cause) { t.Error(e) } e.WithCause(errors.New("different")) if ref.Cause != cause || r.Resolve(apierrors.ByCode("FIRST")).Cause != nil { t.Error("shared state") } }() } wg.Wait() if got := r.Resolve(apierrors.ByCode("SECOND")); got.Message != "second" || got.BusinessCode != int64(400002) { t.Fatal(got) } if got := r.Resolve(apierrors.ByCode("DENIED")); got.Status != 403 || got.BusinessCode != "denied" { t.Fatal(got) } other := apierrors.MustRegistry(apierrors.Catalog{Definitions: map[string]apierrors.Definition{"FIRST": {Status: 409, Code: 9, Message: "other"}}}) if got := other.Resolve(apierrors.ByCode("FIRST")); got.Status != 409 || got.Message != "other" { t.Fatal(got) } if got := r.Resolve(apierrors.ByCode("MISSING").WithCause(cause)); got.Status != 500 || got.Code != "INTERNAL_ERROR" || got.Message == cause.Error() || !errors.Is(got, cause) { t.Fatal(got) } if got := apierrors.From(apierrors.ByCode("MISSING")); got.Status != 500 { t.Fatal(got) } legacy := apierrors.New(422, "OLD", "legacy") if got := r.Resolve(legacy); got == legacy || got.Status != 422 || got.Message != "legacy" { t.Fatal(got) } } func TestCatalogStrictJSONAndValues(t *testing.T) { raw := `{"defaultStatus":200,"definitions":{"LARGE":{"code":9223372036854775807,"message":"large"}}}` var c apierrors.Catalog if err := json.Unmarshal([]byte(raw), &c); err != nil { t.Fatal(err) } e := apierrors.MustRegistry(c).Resolve(apierrors.ByCode("LARGE")) if e.BusinessCode != int64(math.MaxInt64) { t.Fatal(e.BusinessCode) } for _, raw := range []string{ `{"definitions":{"A":{"code":1,"message":"a"},"A":{"code":2,"message":"b"}}}`, `{"definitions":{"A":{"code":1,"code":2,"message":"a"}}}`, `{"definitions":{"A":{"code":1,"message":"a","typo":true}}}`, `{"defaultStatus":204,"definitions":{}}`, `{"definitions":{"A":{"code":1.5,"message":"a"}}}`, `{"definitions":{"A":{"code":9223372036854775808,"message":"a"}}}`, } { var invalid apierrors.Catalog err := json.Unmarshal([]byte(raw), &invalid) if err == nil { err = invalid.Validate() } if err == nil { t.Errorf("accepted %s", raw) } } for _, status := range []int{0, 99, 199, 204, 205, 304, 600} { c := apierrors.Catalog{Definitions: map[string]apierrors.Definition{"A": {Status: status, Code: "a", Message: "a"}}} if status == 0 { continue } if c.Validate() == nil { t.Fatal(status) } } }