feat: add typed error message templates and stable replay snapshots
This commit is contained in:
81
middleware/redis/errorParams_test.go
Normal file
81
middleware/redis/errorParams_test.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
omni "git.yuntuo.hk/k2safe/go-omni"
|
||||
apierrors "git.yuntuo.hk/k2safe/go-omni/errors"
|
||||
"git.yuntuo.hk/k2safe/go-omni/httpx"
|
||||
"git.yuntuo.hk/k2safe/go-omni/middleware"
|
||||
"git.yuntuo.hk/k2safe/go-omni/response"
|
||||
goredis "github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
func TestTemplateErrorRedisReplay(t *testing.T) {
|
||||
address := os.Getenv("GO_OMNI_REDIS_TEST_ADDR")
|
||||
if address == "" {
|
||||
t.Skip("requires isolated Redis")
|
||||
}
|
||||
client := goredis.NewClient(&goredis.Options{Addr: address, PoolSize: 2, MaxRetries: -1})
|
||||
defer client.Close()
|
||||
backend, err := NewIdempotencyStore(client, fmt.Sprint("template:", time.Now().UnixNano()), IdempotencyConfig{MaxEntries: 4})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer client.Del(context.Background(), backend.keys...)
|
||||
var observed []*apierrors.APIError
|
||||
calls := 0
|
||||
var bodies []string
|
||||
for _, template := range []string{"{label}不能超过{max}个字符", "changed: {label}/{max}"} {
|
||||
cat := apierrors.Catalog{DefaultStatus: 200, Definitions: map[string]apierrors.Definition{"LIMIT": {Code: int64(math.MaxInt64), Message: template, Params: []apierrors.Parameter{{Name: "label", Type: "string"}, {Name: "max", Type: "int64"}}}}}
|
||||
app := omni.New(omni.WithErrors(cat), omni.WithResponse(response.Config{CodeType: "int64", SuccessCode: 0, ErrorCode: 9999, MessageField: "msg", DataMode: "always"}))
|
||||
idem, err := middleware.NewIdempotency(middleware.IdempotencyConfig{Store: backend, Scope: func(*httpx.Context) (string, error) { return "fixture", nil }})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
app.Use(func(next httpx.Handler) httpx.Handler {
|
||||
return func(c *httpx.Context) error { err := next(c); observed = append(observed, c.Failure()); return err }
|
||||
})
|
||||
app.POST("/", func(c *httpx.Context) error {
|
||||
calls++
|
||||
return apierrors.ByCode("LIMIT").WithParams(map[string]any{"label": "备注", "max": int64(math.MaxInt64)}).WithCause(errors.New("privateCause"))
|
||||
}, idem.Middleware())
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest("POST", "/", nil)
|
||||
r.Header.Set("Idempotency-Key", "same")
|
||||
app.ServeHTTP(w, r)
|
||||
app.Close()
|
||||
if w.Code != 200 || !strings.Contains(w.Body.String(), "备注不能超过9223372036854775807个字符") || !strings.Contains(w.Body.String(), `"code":9223372036854775807`) || !strings.Contains(w.Body.String(), `"data":null`) || strings.Contains(w.Body.String(), "privateCause") {
|
||||
t.Fatal(w.Code, w.Body.String())
|
||||
}
|
||||
bodies = append(bodies, w.Body.String())
|
||||
}
|
||||
if calls != 1 || bodies[0] != bodies[1] || len(observed) != 2 {
|
||||
t.Fatal(calls, bodies, observed)
|
||||
}
|
||||
for _, e := range observed {
|
||||
if e == nil || e.BusinessCode != int64(math.MaxInt64) || e.Message != "备注不能超过9223372036854775807个字符" {
|
||||
t.Fatal(e)
|
||||
}
|
||||
}
|
||||
if observed[1].Cause != nil {
|
||||
t.Fatal("persisted internal cause")
|
||||
}
|
||||
records, err := client.HVals(context.Background(), backend.keys[0]).Result()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, raw := range records {
|
||||
if strings.Contains(raw, "privateCause") || strings.Contains(raw, `"params"`) {
|
||||
t.Fatal("private replay metadata")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user