Files
go-omni/response/template_test.go
k2safe 7670b222ac feat: release configurable API response contracts
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.
2026-09-23 20:42:38 +08:00

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, &copy); 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
}