Define reusable response bodies in .api with typed code mappings, per-route overrides and matching OpenAPI schemas. Add application and callback encoders, early response policy selection, and isolated remote-consumer release verification. Release v0.8.0-preview after full race, vet, build and local MySQL workflow acceptance.
73 lines
2.7 KiB
Go
73 lines
2.7 KiB
Go
package response_test
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.yuntuo.hk/k2safe/go-omni/response"
|
|
)
|
|
|
|
func TestResponseBodyTemplates(t *testing.T) {
|
|
config := response.Config{
|
|
Body: map[string]any{"success": "${ok}", "payload": "${data}", "meta": map[string]any{"trace": "${requestId}", "status": "${status}"}, "literal": "$${data}", "version": 1},
|
|
ErrorBody: map[string]any{"success": "${ok}", "error": map[string]any{"id": "${code}", "reason": "${message}"}},
|
|
CodeType: "integer", SuccessCode: 0, ErrorCode: 9999, Codes: map[string]any{"DENIED": 403001},
|
|
}
|
|
f := response.MustNew(config)
|
|
config.Body["meta"].(map[string]any)["status"] = "mutated"
|
|
f.Config().ErrorBody["error"].(map[string]any)["reason"] = "mutated"
|
|
good := f.Value(response.Result{Status: 201, Code: "CREATED", Data: []string{"a"}})
|
|
want := map[string]any{"success": true, "payload": []string{"a"}, "meta": map[string]any{"status": 201}, "literal": "${data}", "version": 1}
|
|
if !reflect.DeepEqual(good, want) {
|
|
t.Fatalf("%#v", good)
|
|
}
|
|
bad := f.Value(response.Result{Status: 403, Code: "DENIED", Message: "denied", Err: errors.New("PRIVATE")})
|
|
want = map[string]any{"success": false, "error": map[string]any{"id": int64(403001), "reason": "denied"}}
|
|
if !reflect.DeepEqual(bad, want) {
|
|
t.Fatalf("%#v", bad)
|
|
}
|
|
// Returning or serializing configuration preserves reusable templates, even {}.
|
|
for _, cfg := range []response.Config{f.Config(), {Body: map[string]any{}, ErrorBody: map[string]any{}}, {Body: map[string]any{"values": []any{"${data}", true, nil}}}} {
|
|
before := response.MustNew(cfg).Value(response.Result{Status: 200})
|
|
data, err := json.Marshal(cfg)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var copy response.Config
|
|
if err = json.Unmarshal(data, ©); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
a, _ := json.Marshal(before)
|
|
b, _ := json.Marshal(response.MustNew(copy).Value(response.Result{Status: 200}))
|
|
if string(a) != string(b) {
|
|
t.Fatalf("round trip: %s / %s", a, b)
|
|
}
|
|
}
|
|
cyclic := map[string]any{}
|
|
cyclic["cycle"] = cyclic
|
|
for _, cfg := range []response.Config{
|
|
{Body: map[string]any{"value": "${missing}"}}, {Body: map[string]any{"value": "${data}-suffix"}},
|
|
{Body: cyclic}, {Body: map[string]any{"value": make(chan int)}}, {Body: map[string]any{}, DataField: "data"},
|
|
{ErrorBody: map[string]any{}},
|
|
} {
|
|
if _, err := response.New(cfg.Clone()); err == nil {
|
|
t.Fatal("invalid template accepted")
|
|
}
|
|
}
|
|
if strings.Contains(string(mustJSON(t, bad)), "PRIVATE") {
|
|
t.Fatal("private cause leaked")
|
|
}
|
|
}
|
|
|
|
func mustJSON(t *testing.T, value any) []byte {
|
|
t.Helper()
|
|
data, err := json.Marshal(value)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return data
|
|
}
|