Skip to content

Commit 750df79

Browse files
committed
feat:添加对gin的所有组件的测试
1 parent 70ae11a commit 750df79

2 files changed

Lines changed: 315 additions & 21 deletions

File tree

pkg/transport/http/ginx/ginx.go

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package ginx
22

33
import (
4-
"context"
54
"github.com/gin-contrib/pprof"
65
"github.com/gin-gonic/gin"
76
"github.com/muxi-Infra/muxi-micro/pkg/errs"
@@ -21,9 +20,8 @@ var (
2120
)
2221

2322
type engineConfig struct {
24-
env t_http.Env
25-
enablePprof bool
26-
g *gin.Engine
23+
env t_http.Env
24+
g *gin.Engine
2725
}
2826

2927
type EngineOption func(*engineConfig)
@@ -32,17 +30,6 @@ type EngineOption func(*engineConfig)
3230
func WithEnv(env t_http.Env) EngineOption {
3331
return func(cfg *engineConfig) {
3432
cfg.env = env
35-
// dev 和 test 环境默认开启 pprof
36-
if env == t_http.EnvDev || env == t_http.EnvTest {
37-
cfg.enablePprof = true
38-
}
39-
}
40-
}
41-
42-
// 手动控制是否开启 pprof
43-
func WithPprof(enable bool) EngineOption {
44-
return func(cfg *engineConfig) {
45-
cfg.enablePprof = enable
4633
}
4734
}
4835

@@ -56,17 +43,16 @@ func WithEngine(g *gin.Engine) EngineOption {
5643
// 创建默认引擎,附带常用中间件和可选配置
5744
func NewDefaultEngine(opts ...EngineOption) *gin.Engine {
5845
cfg := &engineConfig{
59-
env: t_http.EnvProd,
60-
enablePprof: false,
61-
g: gin.Default(),
46+
env: t_http.EnvProd,
47+
g: gin.Default(),
6248
}
6349

6450
for _, opt := range opts {
6551
opt(cfg)
6652
}
6753

6854
// 非生产环境注册 pprof
69-
if cfg.enablePprof {
55+
if cfg.env != t_http.EnvProd {
7056
pprof.Register(cfg.g)
7157
}
7258

@@ -106,7 +92,7 @@ func bind(ctx *gin.Context, req any) error {
10692
return nil
10793
}
10894

109-
func WrapClaimsAndReq[Req any, UserClaims any](getClaims func(ctx context.Context) (UserClaims, error), fn func(*gin.Context, Req, UserClaims) t_http.Response) gin.HandlerFunc {
95+
func WrapClaimsAndReq[Req any, UserClaims any](getClaims func(*gin.Context) (UserClaims, error), fn func(*gin.Context, Req, UserClaims) t_http.Response) gin.HandlerFunc {
11096
return func(ctx *gin.Context) {
11197

11298
// 检查前置中间件是否存在错误,如果存在应当直接返回
@@ -189,7 +175,7 @@ func Wrap(fn func(*gin.Context) t_http.Response) gin.HandlerFunc {
189175

190176
// WrapClaims 用于处理有用户验证但是没有请求体的请求
191177
// ctx表示上下文,Resp表示响应结构体(这里全部填web.Response),UserClaims表示用户信息
192-
func WrapClaims[UserClaims any](getClaims func(ctx context.Context) (UserClaims, error), fn func(*gin.Context, UserClaims) t_http.Response) gin.HandlerFunc {
178+
func WrapClaims[UserClaims any](getClaims func(ctx *gin.Context) (UserClaims, error), fn func(*gin.Context, UserClaims) t_http.Response) gin.HandlerFunc {
193179
return func(ctx *gin.Context) {
194180

195181
//检查前置中间件是否存在错误,如果存在应当直接返回
Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
package ginx
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"errors"
7+
"net/http"
8+
"net/http/httptest"
9+
"testing"
10+
11+
"github.com/gin-gonic/gin"
12+
t_http "github.com/muxi-Infra/muxi-micro/pkg/transport/http"
13+
"github.com/stretchr/testify/assert"
14+
)
15+
16+
type demoReq struct {
17+
Name string `json:"name" form:"name" binding:"required"`
18+
}
19+
20+
type demoClaims struct {
21+
UID int64 `json:"uid"`
22+
}
23+
24+
func handleDemoReq(c *gin.Context, r demoReq) t_http.Response {
25+
return t_http.Response{
26+
HttpCode: http.StatusOK,
27+
CommonResp: t_http.CommonResp{
28+
Code: 0,
29+
Message: "ok",
30+
Data: r,
31+
},
32+
}
33+
}
34+
35+
func handleWithClaims(c *gin.Context, uc demoClaims) t_http.Response {
36+
return t_http.Response{
37+
HttpCode: http.StatusOK,
38+
CommonResp: t_http.CommonResp{
39+
Code: 0,
40+
Message: "ok",
41+
Data: uc,
42+
},
43+
}
44+
}
45+
46+
func handleWithClaimsAndReq(c *gin.Context, r demoReq, uc demoClaims) t_http.Response {
47+
return t_http.Response{
48+
HttpCode: http.StatusOK,
49+
CommonResp: t_http.CommonResp{
50+
Code: 0,
51+
Message: "ok",
52+
Data: gin.H{
53+
"name": r.Name,
54+
"uid": uc.UID,
55+
},
56+
},
57+
}
58+
}
59+
60+
func handleNoBody(c *gin.Context) t_http.Response {
61+
return t_http.Response{
62+
HttpCode: http.StatusOK,
63+
CommonResp: t_http.CommonResp{Code: 0, Message: "pong"},
64+
}
65+
}
66+
67+
func mockClaimsOK(c *gin.Context) (demoClaims, error) {
68+
return demoClaims{UID: 1}, nil
69+
}
70+
71+
var authError = errors.New("认证失败")
72+
73+
func mockClaimsFail(c *gin.Context) (demoClaims, error) {
74+
return demoClaims{}, authError
75+
}
76+
77+
func decodeResp[T any](body []byte) (T, error) {
78+
var resp t_http.CommonResp
79+
var t T
80+
err := json.Unmarshal(body, &resp)
81+
if err != nil {
82+
return t, err
83+
}
84+
db, _ := json.Marshal(resp.Data)
85+
err = json.Unmarshal(db, &t)
86+
return t, err
87+
}
88+
89+
func setErrorHandlerFunc() gin.HandlerFunc {
90+
return func(c *gin.Context) {
91+
c.Error(errors.New("test"))
92+
}
93+
}
94+
95+
func TestWrapReq(t *testing.T) {
96+
gin.SetMode(gin.TestMode)
97+
g := gin.New()
98+
g.POST("/demo", WrapReq(handleDemoReq))
99+
100+
tests := []struct {
101+
name string
102+
body string
103+
status int
104+
expect string
105+
}{
106+
{"非法参数", `{"foo":"bar"}`, http.StatusBadRequest, ""},
107+
{"正常参数", `{"name":"bar"}`, http.StatusOK, "bar"},
108+
}
109+
110+
for _, tt := range tests {
111+
t.Run(tt.name, func(t *testing.T) {
112+
w := httptest.NewRecorder()
113+
req, _ := http.NewRequest(http.MethodPost, "/demo", bytes.NewBufferString(tt.body))
114+
req.Header.Set("Content-Type", "application/json")
115+
g.ServeHTTP(w, req)
116+
117+
assert.Equal(t, tt.status, w.Code)
118+
if tt.status == http.StatusOK {
119+
res, err := decodeResp[demoReq](w.Body.Bytes())
120+
assert.NoError(t, err)
121+
assert.Equal(t, tt.expect, res.Name)
122+
}
123+
})
124+
}
125+
126+
t.Run("error in front Middleware", func(t *testing.T) {
127+
g.GET("/testFrontError", setErrorHandlerFunc(), WrapReq(handleDemoReq))
128+
w := httptest.NewRecorder()
129+
req, _ := http.NewRequest(http.MethodGet, "/testFrontError", bytes.NewBufferString("{\"name\":\"bar\"}"))
130+
req.Header.Set("Content-Type", "application/json")
131+
g.ServeHTTP(w, req)
132+
133+
assert.Equal(t, http.StatusOK, w.Code)
134+
135+
})
136+
}
137+
138+
func TestWrapClaims(t *testing.T) {
139+
gin.SetMode(gin.TestMode)
140+
g := gin.New()
141+
g.GET("/ok", WrapClaims(mockClaimsOK, handleWithClaims))
142+
g.GET("/fail", WrapClaims(mockClaimsFail, handleWithClaims))
143+
144+
t.Run("验证失败", func(t *testing.T) {
145+
w := httptest.NewRecorder()
146+
req, _ := http.NewRequest(http.MethodGet, "/fail", nil)
147+
g.ServeHTTP(w, req)
148+
149+
var resp t_http.CommonResp
150+
_ = json.Unmarshal(w.Body.Bytes(), &resp)
151+
assert.Equal(t, http.StatusUnauthorized, w.Code)
152+
assert.Equal(t, "登陆状态异常:"+authError.Error(), resp.Message)
153+
})
154+
155+
t.Run("验证成功", func(t *testing.T) {
156+
w := httptest.NewRecorder()
157+
req, _ := http.NewRequest(http.MethodGet, "/ok", nil)
158+
g.ServeHTTP(w, req)
159+
assert.Equal(t, http.StatusOK, w.Code)
160+
})
161+
162+
t.Run("error in front Middleware", func(t *testing.T) {
163+
g.GET("/testFrontError", setErrorHandlerFunc(), WrapClaims(mockClaimsOK, handleWithClaims))
164+
w := httptest.NewRecorder()
165+
req, _ := http.NewRequest(http.MethodGet, "/testFrontError", bytes.NewBufferString("{\"name\":\"bar\"}"))
166+
req.Header.Set("Content-Type", "application/json")
167+
g.ServeHTTP(w, req)
168+
169+
assert.Equal(t, http.StatusOK, w.Code)
170+
171+
})
172+
}
173+
174+
func TestWrapClaimsAndReq(t *testing.T) {
175+
gin.SetMode(gin.TestMode)
176+
g := gin.New()
177+
g.POST("/full", WrapClaimsAndReq(mockClaimsOK, handleWithClaimsAndReq))
178+
g.GET("/full", WrapClaimsAndReq(mockClaimsOK, handleWithClaimsAndReq))
179+
g.POST("/fail", WrapClaimsAndReq(mockClaimsFail, handleWithClaimsAndReq))
180+
181+
t.Run("Success get", func(t *testing.T) {
182+
w := httptest.NewRecorder()
183+
req, _ := http.NewRequest(http.MethodGet, "/full", bytes.NewBufferString(`{"name":"bob"}`))
184+
req.Header.Set("Content-Type", "application/json")
185+
g.ServeHTTP(w, req)
186+
187+
assert.Equal(t, http.StatusOK, w.Code)
188+
189+
var resp t_http.CommonResp
190+
_ = json.Unmarshal(w.Body.Bytes(), &resp)
191+
db, _ := json.Marshal(resp.Data)
192+
var m map[string]any
193+
_ = json.Unmarshal(db, &m)
194+
195+
assert.Equal(t, "ok", resp.Message)
196+
assert.Equal(t, "bob", m["name"])
197+
assert.Equal(t, float64(1), m["uid"]) // json.Unmarshal => float64
198+
})
199+
200+
t.Run("Success post", func(t *testing.T) {
201+
w := httptest.NewRecorder()
202+
req, _ := http.NewRequest(http.MethodPost, "/full", bytes.NewBufferString(`{"name":"bob"}`))
203+
req.Header.Set("Content-Type", "application/json")
204+
g.ServeHTTP(w, req)
205+
206+
assert.Equal(t, http.StatusOK, w.Code)
207+
208+
var resp t_http.CommonResp
209+
_ = json.Unmarshal(w.Body.Bytes(), &resp)
210+
db, _ := json.Marshal(resp.Data)
211+
var m map[string]any
212+
_ = json.Unmarshal(db, &m)
213+
214+
assert.Equal(t, "ok", resp.Message)
215+
assert.Equal(t, "bob", m["name"])
216+
assert.Equal(t, float64(1), m["uid"]) // json.Unmarshal => float64
217+
})
218+
219+
t.Run("BindErr", func(t *testing.T) {
220+
w := httptest.NewRecorder()
221+
req, _ := http.NewRequest(http.MethodPost, "/full", bytes.NewBufferString(`{}`))
222+
req.Header.Set("Content-Type", "application/json")
223+
g.ServeHTTP(w, req)
224+
assert.Equal(t, http.StatusBadRequest, w.Code)
225+
})
226+
227+
t.Run("AuthErr", func(t *testing.T) {
228+
w := httptest.NewRecorder()
229+
req, _ := http.NewRequest(http.MethodPost, "/fail", bytes.NewBufferString(`{"name":"alice"}`))
230+
req.Header.Set("Content-Type", "application/json")
231+
g.ServeHTTP(w, req)
232+
233+
var resp t_http.CommonResp
234+
_ = json.Unmarshal(w.Body.Bytes(), &resp)
235+
assert.Equal(t, http.StatusUnauthorized, w.Code)
236+
assert.Equal(t, "登陆状态异常:"+authError.Error(), resp.Message)
237+
})
238+
239+
t.Run("error in front Middleware", func(t *testing.T) {
240+
g.GET("/testFrontError", setErrorHandlerFunc(), WrapClaimsAndReq(mockClaimsOK, handleWithClaimsAndReq))
241+
w := httptest.NewRecorder()
242+
req, _ := http.NewRequest(http.MethodGet, "/testFrontError", bytes.NewBufferString("{\"name\":\"bar\"}"))
243+
req.Header.Set("Content-Type", "application/json")
244+
g.ServeHTTP(w, req)
245+
246+
assert.Equal(t, http.StatusOK, w.Code)
247+
248+
})
249+
}
250+
251+
func TestWrap(t *testing.T) {
252+
gin.SetMode(gin.TestMode)
253+
g := gin.New()
254+
255+
t.Run("请求正常", func(t *testing.T) {
256+
g.GET("/ping", Wrap(handleNoBody))
257+
258+
w := httptest.NewRecorder()
259+
req, _ := http.NewRequest(http.MethodGet, "/ping", nil)
260+
g.ServeHTTP(w, req)
261+
262+
assert.Equal(t, http.StatusOK, w.Code)
263+
var resp t_http.CommonResp
264+
_ = json.Unmarshal(w.Body.Bytes(), &resp)
265+
assert.Equal(t, "pong", resp.Message)
266+
})
267+
268+
t.Run("error in front Middleware", func(t *testing.T) {
269+
g.GET("/testFrontError", setErrorHandlerFunc(), Wrap(handleNoBody))
270+
w := httptest.NewRecorder()
271+
req, _ := http.NewRequest(http.MethodGet, "/testFrontError", bytes.NewBufferString("{\"name\":\"bar\"}"))
272+
req.Header.Set("Content-Type", "application/json")
273+
g.ServeHTTP(w, req)
274+
275+
assert.Equal(t, http.StatusOK, w.Code)
276+
277+
})
278+
279+
}
280+
281+
func TestDefaultEngine(t *testing.T) {
282+
t.Run("pprof enabled", func(t *testing.T) {
283+
g := NewDefaultEngine(
284+
WithEngine(gin.Default()),
285+
WithEnv(t_http.EnvTest),
286+
)
287+
g.GET("/ping", Wrap(handleNoBody))
288+
w := httptest.NewRecorder()
289+
req, _ := http.NewRequest(http.MethodGet, "/debug/pprof/", nil)
290+
g.ServeHTTP(w, req)
291+
assert.Equal(t, http.StatusOK, w.Code)
292+
})
293+
294+
t.Run("pprof disabled", func(t *testing.T) {
295+
g := NewDefaultEngine(
296+
WithEngine(gin.Default()),
297+
WithEnv(t_http.EnvProd),
298+
)
299+
SetBindErrCode(42062)
300+
SetGetClaimsErrCode(12345)
301+
UseDefaultMiddleware(g)
302+
g.GET("/ping", Wrap(handleNoBody))
303+
w := httptest.NewRecorder()
304+
req, _ := http.NewRequest(http.MethodGet, "/debug/pprof/", nil)
305+
g.ServeHTTP(w, req)
306+
assert.Equal(t, http.StatusNotFound, w.Code)
307+
})
308+
}

0 commit comments

Comments
 (0)