| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889 |
- package httpx
- import (
- "errors"
- "net/http"
- "net/http/httptest"
- "strings"
- "testing"
- "github.com/gin-gonic/gin"
- "github.com/kairo/haven/internal/auth"
- )
- func contains(s, sub string) bool { return strings.Contains(s, sub) }
- func init() { gin.SetMode(gin.TestMode) }
- func run(fn func(c *gin.Context)) *httptest.ResponseRecorder {
- r := gin.New()
- r.GET("/x", fn)
- w := httptest.NewRecorder()
- r.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/x", nil))
- return w
- }
- func TestFailMapping(t *testing.T) {
- cases := []struct {
- name string
- err error
- wantCode int
- wantBody string
- }{
- {name: "凭据错误", err: auth.ErrInvalidCredentials, wantCode: 401, wantBody: "unauthorized"},
- {name: "临时token过期", err: auth.ErrPendingExpired, wantCode: 401, wantBody: "unauthorized"},
- {name: "动态码错误", err: auth.ErrBadTotp, wantCode: 401, wantBody: "unauthorized"},
- {name: "TOTP已启用", err: auth.ErrTotpAlreadyEnabled, wantCode: 409, wantBody: "conflict"},
- {name: "TOTP未启用", err: auth.ErrTotpNotEnabled, wantCode: 422, wantBody: "validation_failed"},
- {name: "找不到", err: auth.ErrNotFound, wantCode: 404, wantBody: "not_found"},
- {name: "未知错误", err: errors.New("boom"), wantCode: 500, wantBody: "internal_error"},
- }
- for _, tc := range cases {
- t.Run(tc.name, func(t *testing.T) {
- w := run(func(c *gin.Context) { Fail(c, tc.err) })
- if w.Code != tc.wantCode {
- t.Errorf("状态码 = %d,期望 %d", w.Code, tc.wantCode)
- }
- if body := w.Body.String(); !contains(body, tc.wantBody) {
- t.Errorf("响应体应含 %q,实际 %s", tc.wantBody, body)
- }
- // 内部错误细节不能泄漏给客户端
- if tc.wantCode == 500 && contains(w.Body.String(), "boom") {
- t.Error("500 响应不应包含内部错误细节")
- }
- })
- }
- }
- func TestFailValidationFields(t *testing.T) {
- w := run(func(c *gin.Context) {
- FailValidation(c, map[string]string{"title": "不能为空"})
- })
- if w.Code != http.StatusUnprocessableEntity {
- t.Errorf("状态码 = %d", w.Code)
- }
- if body := w.Body.String(); !contains(body, `"fields":{"title":"不能为空"}`) {
- t.Errorf("应带 fields,实际 %s", body)
- }
- }
- func TestInstallAuthErrorHandlers(t *testing.T) {
- InstallAuthErrorHandlers()
- // 用 RequireLogin 触发 401,不需要数据库
- r := gin.New()
- r.Use(auth.Authenticate(nil), auth.RequireLogin())
- r.GET("/p", func(c *gin.Context) { c.Status(http.StatusOK) })
- w := httptest.NewRecorder()
- r.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/p", nil))
- if w.Code != http.StatusUnauthorized {
- t.Fatalf("状态码 = %d", w.Code)
- }
- if body := w.Body.String(); !contains(body, `"error":"unauthorized"`) {
- t.Errorf("应走 httpx 的统一错误体,实际 %s", body)
- }
- }
|