69 lines
2.3 KiB
Go
69 lines
2.3 KiB
Go
package unifiedapi
|
||
|
||
import (
|
||
"encoding/json"
|
||
"io"
|
||
"log/slog"
|
||
"net/http"
|
||
"net/http/httptest"
|
||
"strings"
|
||
"testing"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
)
|
||
|
||
func do(t *testing.T, r http.Handler, method, path, body string, hdr map[string]string) (int, Body, http.Header) {
|
||
t.Helper()
|
||
req := httptest.NewRequest(method, path, strings.NewReader(body))
|
||
req.Header.Set("Content-Type", "application/json")
|
||
for k, v := range hdr {
|
||
req.Header.Set(k, v)
|
||
}
|
||
w := httptest.NewRecorder()
|
||
r.ServeHTTP(w, req)
|
||
var b Body
|
||
if err := json.Unmarshal(w.Body.Bytes(), &b); err != nil {
|
||
t.Fatalf("响应不是统一格式:%s", w.Body.String())
|
||
}
|
||
return w.Code, b, w.Header()
|
||
}
|
||
|
||
func TestUnifiedResponses(t *testing.T) {
|
||
gin.SetMode(gin.TestMode)
|
||
r := New(slog.New(slog.NewTextHandler(io.Discard, nil)))
|
||
|
||
cases := []struct {
|
||
name, method, path, body string
|
||
status, code int
|
||
msgContains string
|
||
}{
|
||
{"创建成功", "POST", "/api/tasks", `{"title":"写周报","priority":"high"}`, 201, 0, "ok"},
|
||
{"必填校验", "POST", "/api/tasks", `{"title":""}`, 400, 40000, "title 不能为空"},
|
||
{"枚举校验", "POST", "/api/tasks", `{"title":"a","priority":"urgent"}`, 400, 40000, "priority 只能是 low normal high 之一"},
|
||
{"JSON 格式错误", "POST", "/api/tasks", `{"title":`, 400, 40000, "请求体格式错误"},
|
||
{"查询存在", "GET", "/api/tasks/1", "", 200, 0, "ok"},
|
||
{"查询不存在", "GET", "/api/tasks/999", "", 404, 40400, "资源不存在"},
|
||
{"panic 兜底", "GET", "/api/boom", "", 500, 50000, "服务器开小差"},
|
||
{"内部错误不泄漏细节", "GET", "/api/db-error", "", 500, 50000, "服务器开小差"},
|
||
}
|
||
for _, c := range cases {
|
||
t.Run(c.name, func(t *testing.T) {
|
||
status, b, h := do(t, r, c.method, c.path, c.body, nil)
|
||
if status != c.status || b.Code != c.code || !strings.Contains(b.Message, c.msgContains) {
|
||
t.Fatalf("got %d %+v", status, b)
|
||
}
|
||
if b.RequestID == "" || h.Get("X-Request-ID") != b.RequestID {
|
||
t.Fatalf("缺少请求 ID:%+v", b)
|
||
}
|
||
if strings.Contains(b.Message, "secret") || strings.Contains(b.Message, "relation") {
|
||
t.Fatal("内部错误细节泄漏给了用户")
|
||
}
|
||
})
|
||
}
|
||
|
||
_, b, _ := do(t, r, "GET", "/api/tasks/1", "", map[string]string{"X-Request-ID": "trace-123"})
|
||
if b.RequestID != "trace-123" {
|
||
t.Fatalf("应沿用客户端的请求 ID,得到 %q", b.RequestID)
|
||
}
|
||
}
|