1
2
3
4
5 package gin
6
7 import (
8 "fmt"
9 "math/rand"
10 "net/http"
11 "net/http/httptest"
12 "os"
13 "strings"
14 "testing"
15
16 "github.com/stretchr/testify/assert"
17 )
18
19 type route struct {
20 method string
21 path string
22 }
23
24
25 var githubAPI = []route{
26
27 {http.MethodGet, "/authorizations"},
28 {http.MethodGet, "/authorizations/:id"},
29 {http.MethodPost, "/authorizations"},
30
31
32 {http.MethodDelete, "/authorizations/:id"},
33 {http.MethodGet, "/applications/:client_id/tokens/:access_token"},
34 {http.MethodDelete, "/applications/:client_id/tokens"},
35 {http.MethodDelete, "/applications/:client_id/tokens/:access_token"},
36
37
38 {http.MethodGet, "/events"},
39 {http.MethodGet, "/repos/:owner/:repo/events"},
40 {http.MethodGet, "/networks/:owner/:repo/events"},
41 {http.MethodGet, "/orgs/:org/events"},
42 {http.MethodGet, "/users/:user/received_events"},
43 {http.MethodGet, "/users/:user/received_events/public"},
44 {http.MethodGet, "/users/:user/events"},
45 {http.MethodGet, "/users/:user/events/public"},
46 {http.MethodGet, "/users/:user/events/orgs/:org"},
47 {http.MethodGet, "/feeds"},
48 {http.MethodGet, "/notifications"},
49 {http.MethodGet, "/repos/:owner/:repo/notifications"},
50 {http.MethodPut, "/notifications"},
51 {http.MethodPut, "/repos/:owner/:repo/notifications"},
52 {http.MethodGet, "/notifications/threads/:id"},
53
54 {http.MethodGet, "/notifications/threads/:id/subscription"},
55 {http.MethodPut, "/notifications/threads/:id/subscription"},
56 {http.MethodDelete, "/notifications/threads/:id/subscription"},
57 {http.MethodGet, "/repos/:owner/:repo/stargazers"},
58 {http.MethodGet, "/users/:user/starred"},
59 {http.MethodGet, "/user/starred"},
60 {http.MethodGet, "/user/starred/:owner/:repo"},
61 {http.MethodPut, "/user/starred/:owner/:repo"},
62 {http.MethodDelete, "/user/starred/:owner/:repo"},
63 {http.MethodGet, "/repos/:owner/:repo/subscribers"},
64 {http.MethodGet, "/users/:user/subscriptions"},
65 {http.MethodGet, "/user/subscriptions"},
66 {http.MethodGet, "/repos/:owner/:repo/subscription"},
67 {http.MethodPut, "/repos/:owner/:repo/subscription"},
68 {http.MethodDelete, "/repos/:owner/:repo/subscription"},
69 {http.MethodGet, "/user/subscriptions/:owner/:repo"},
70 {http.MethodPut, "/user/subscriptions/:owner/:repo"},
71 {http.MethodDelete, "/user/subscriptions/:owner/:repo"},
72
73
74 {http.MethodGet, "/users/:user/gists"},
75 {http.MethodGet, "/gists"},
76
77
78 {http.MethodGet, "/gists/:id"},
79 {http.MethodPost, "/gists"},
80
81 {http.MethodPut, "/gists/:id/star"},
82 {http.MethodDelete, "/gists/:id/star"},
83 {http.MethodGet, "/gists/:id/star"},
84 {http.MethodPost, "/gists/:id/forks"},
85 {http.MethodDelete, "/gists/:id"},
86
87
88 {http.MethodGet, "/repos/:owner/:repo/git/blobs/:sha"},
89 {http.MethodPost, "/repos/:owner/:repo/git/blobs"},
90 {http.MethodGet, "/repos/:owner/:repo/git/commits/:sha"},
91 {http.MethodPost, "/repos/:owner/:repo/git/commits"},
92
93 {http.MethodGet, "/repos/:owner/:repo/git/refs"},
94 {http.MethodPost, "/repos/:owner/:repo/git/refs"},
95
96
97 {http.MethodGet, "/repos/:owner/:repo/git/tags/:sha"},
98 {http.MethodPost, "/repos/:owner/:repo/git/tags"},
99 {http.MethodGet, "/repos/:owner/:repo/git/trees/:sha"},
100 {http.MethodPost, "/repos/:owner/:repo/git/trees"},
101
102
103 {http.MethodGet, "/issues"},
104 {http.MethodGet, "/user/issues"},
105 {http.MethodGet, "/orgs/:org/issues"},
106 {http.MethodGet, "/repos/:owner/:repo/issues"},
107 {http.MethodGet, "/repos/:owner/:repo/issues/:number"},
108 {http.MethodPost, "/repos/:owner/:repo/issues"},
109
110 {http.MethodGet, "/repos/:owner/:repo/assignees"},
111 {http.MethodGet, "/repos/:owner/:repo/assignees/:assignee"},
112 {http.MethodGet, "/repos/:owner/:repo/issues/:number/comments"},
113
114
115 {http.MethodPost, "/repos/:owner/:repo/issues/:number/comments"},
116
117
118 {http.MethodGet, "/repos/:owner/:repo/issues/:number/events"},
119
120
121 {http.MethodGet, "/repos/:owner/:repo/labels"},
122 {http.MethodGet, "/repos/:owner/:repo/labels/:name"},
123 {http.MethodPost, "/repos/:owner/:repo/labels"},
124
125 {http.MethodDelete, "/repos/:owner/:repo/labels/:name"},
126 {http.MethodGet, "/repos/:owner/:repo/issues/:number/labels"},
127 {http.MethodPost, "/repos/:owner/:repo/issues/:number/labels"},
128 {http.MethodDelete, "/repos/:owner/:repo/issues/:number/labels/:name"},
129 {http.MethodPut, "/repos/:owner/:repo/issues/:number/labels"},
130 {http.MethodDelete, "/repos/:owner/:repo/issues/:number/labels"},
131 {http.MethodGet, "/repos/:owner/:repo/milestones/:number/labels"},
132 {http.MethodGet, "/repos/:owner/:repo/milestones"},
133 {http.MethodGet, "/repos/:owner/:repo/milestones/:number"},
134 {http.MethodPost, "/repos/:owner/:repo/milestones"},
135
136 {http.MethodDelete, "/repos/:owner/:repo/milestones/:number"},
137
138
139 {http.MethodGet, "/emojis"},
140 {http.MethodGet, "/gitignore/templates"},
141 {http.MethodGet, "/gitignore/templates/:name"},
142 {http.MethodPost, "/markdown"},
143 {http.MethodPost, "/markdown/raw"},
144 {http.MethodGet, "/meta"},
145 {http.MethodGet, "/rate_limit"},
146
147
148 {http.MethodGet, "/users/:user/orgs"},
149 {http.MethodGet, "/user/orgs"},
150 {http.MethodGet, "/orgs/:org"},
151
152 {http.MethodGet, "/orgs/:org/members"},
153 {http.MethodGet, "/orgs/:org/members/:user"},
154 {http.MethodDelete, "/orgs/:org/members/:user"},
155 {http.MethodGet, "/orgs/:org/public_members"},
156 {http.MethodGet, "/orgs/:org/public_members/:user"},
157 {http.MethodPut, "/orgs/:org/public_members/:user"},
158 {http.MethodDelete, "/orgs/:org/public_members/:user"},
159 {http.MethodGet, "/orgs/:org/teams"},
160 {http.MethodGet, "/teams/:id"},
161 {http.MethodPost, "/orgs/:org/teams"},
162
163 {http.MethodDelete, "/teams/:id"},
164 {http.MethodGet, "/teams/:id/members"},
165 {http.MethodGet, "/teams/:id/members/:user"},
166 {http.MethodPut, "/teams/:id/members/:user"},
167 {http.MethodDelete, "/teams/:id/members/:user"},
168 {http.MethodGet, "/teams/:id/repos"},
169 {http.MethodGet, "/teams/:id/repos/:owner/:repo"},
170 {http.MethodPut, "/teams/:id/repos/:owner/:repo"},
171 {http.MethodDelete, "/teams/:id/repos/:owner/:repo"},
172 {http.MethodGet, "/user/teams"},
173
174
175 {http.MethodGet, "/repos/:owner/:repo/pulls"},
176 {http.MethodGet, "/repos/:owner/:repo/pulls/:number"},
177 {http.MethodPost, "/repos/:owner/:repo/pulls"},
178
179 {http.MethodGet, "/repos/:owner/:repo/pulls/:number/commits"},
180 {http.MethodGet, "/repos/:owner/:repo/pulls/:number/files"},
181 {http.MethodGet, "/repos/:owner/:repo/pulls/:number/merge"},
182 {http.MethodPut, "/repos/:owner/:repo/pulls/:number/merge"},
183 {http.MethodGet, "/repos/:owner/:repo/pulls/:number/comments"},
184
185
186 {http.MethodPut, "/repos/:owner/:repo/pulls/:number/comments"},
187
188
189
190
191 {http.MethodGet, "/user/repos"},
192 {http.MethodGet, "/users/:user/repos"},
193 {http.MethodGet, "/orgs/:org/repos"},
194 {http.MethodGet, "/repositories"},
195 {http.MethodPost, "/user/repos"},
196 {http.MethodPost, "/orgs/:org/repos"},
197 {http.MethodGet, "/repos/:owner/:repo"},
198
199 {http.MethodGet, "/repos/:owner/:repo/contributors"},
200 {http.MethodGet, "/repos/:owner/:repo/languages"},
201 {http.MethodGet, "/repos/:owner/:repo/teams"},
202 {http.MethodGet, "/repos/:owner/:repo/tags"},
203 {http.MethodGet, "/repos/:owner/:repo/branches"},
204 {http.MethodGet, "/repos/:owner/:repo/branches/:branch"},
205 {http.MethodDelete, "/repos/:owner/:repo"},
206 {http.MethodGet, "/repos/:owner/:repo/collaborators"},
207 {http.MethodGet, "/repos/:owner/:repo/collaborators/:user"},
208 {http.MethodPut, "/repos/:owner/:repo/collaborators/:user"},
209 {http.MethodDelete, "/repos/:owner/:repo/collaborators/:user"},
210 {http.MethodGet, "/repos/:owner/:repo/comments"},
211 {http.MethodGet, "/repos/:owner/:repo/commits/:sha/comments"},
212 {http.MethodPost, "/repos/:owner/:repo/commits/:sha/comments"},
213 {http.MethodGet, "/repos/:owner/:repo/comments/:id"},
214
215 {http.MethodDelete, "/repos/:owner/:repo/comments/:id"},
216 {http.MethodGet, "/repos/:owner/:repo/commits"},
217 {http.MethodGet, "/repos/:owner/:repo/commits/:sha"},
218 {http.MethodGet, "/repos/:owner/:repo/readme"},
219
220
221
222
223 {http.MethodGet, "/repos/:owner/:repo/keys"},
224 {http.MethodGet, "/repos/:owner/:repo/keys/:id"},
225 {http.MethodPost, "/repos/:owner/:repo/keys"},
226
227 {http.MethodDelete, "/repos/:owner/:repo/keys/:id"},
228 {http.MethodGet, "/repos/:owner/:repo/downloads"},
229 {http.MethodGet, "/repos/:owner/:repo/downloads/:id"},
230 {http.MethodDelete, "/repos/:owner/:repo/downloads/:id"},
231 {http.MethodGet, "/repos/:owner/:repo/forks"},
232 {http.MethodPost, "/repos/:owner/:repo/forks"},
233 {http.MethodGet, "/repos/:owner/:repo/hooks"},
234 {http.MethodGet, "/repos/:owner/:repo/hooks/:id"},
235 {http.MethodPost, "/repos/:owner/:repo/hooks"},
236
237 {http.MethodPost, "/repos/:owner/:repo/hooks/:id/tests"},
238 {http.MethodDelete, "/repos/:owner/:repo/hooks/:id"},
239 {http.MethodPost, "/repos/:owner/:repo/merges"},
240 {http.MethodGet, "/repos/:owner/:repo/releases"},
241 {http.MethodGet, "/repos/:owner/:repo/releases/:id"},
242 {http.MethodPost, "/repos/:owner/:repo/releases"},
243
244 {http.MethodDelete, "/repos/:owner/:repo/releases/:id"},
245 {http.MethodGet, "/repos/:owner/:repo/releases/:id/assets"},
246 {http.MethodGet, "/repos/:owner/:repo/stats/contributors"},
247 {http.MethodGet, "/repos/:owner/:repo/stats/commit_activity"},
248 {http.MethodGet, "/repos/:owner/:repo/stats/code_frequency"},
249 {http.MethodGet, "/repos/:owner/:repo/stats/participation"},
250 {http.MethodGet, "/repos/:owner/:repo/stats/punch_card"},
251 {http.MethodGet, "/repos/:owner/:repo/statuses/:ref"},
252 {http.MethodPost, "/repos/:owner/:repo/statuses/:ref"},
253
254
255 {http.MethodGet, "/search/repositories"},
256 {http.MethodGet, "/search/code"},
257 {http.MethodGet, "/search/issues"},
258 {http.MethodGet, "/search/users"},
259 {http.MethodGet, "/legacy/issues/search/:owner/:repository/:state/:keyword"},
260 {http.MethodGet, "/legacy/repos/search/:keyword"},
261 {http.MethodGet, "/legacy/user/search/:keyword"},
262 {http.MethodGet, "/legacy/user/email/:email"},
263
264
265 {http.MethodGet, "/users/:user"},
266 {http.MethodGet, "/user"},
267
268 {http.MethodGet, "/users"},
269 {http.MethodGet, "/user/emails"},
270 {http.MethodPost, "/user/emails"},
271 {http.MethodDelete, "/user/emails"},
272 {http.MethodGet, "/users/:user/followers"},
273 {http.MethodGet, "/user/followers"},
274 {http.MethodGet, "/users/:user/following"},
275 {http.MethodGet, "/user/following"},
276 {http.MethodGet, "/user/following/:user"},
277 {http.MethodGet, "/users/:user/following/:target_user"},
278 {http.MethodPut, "/user/following/:user"},
279 {http.MethodDelete, "/user/following/:user"},
280 {http.MethodGet, "/users/:user/keys"},
281 {http.MethodGet, "/user/keys"},
282 {http.MethodGet, "/user/keys/:id"},
283 {http.MethodPost, "/user/keys"},
284
285 {http.MethodDelete, "/user/keys/:id"},
286 }
287
288 func TestShouldBindUri(t *testing.T) {
289 DefaultWriter = os.Stdout
290 router := New()
291
292 type Person struct {
293 Name string `uri:"name" binding:"required"`
294 ID string `uri:"id" binding:"required"`
295 }
296 router.Handle(http.MethodGet, "/rest/:name/:id", func(c *Context) {
297 var person Person
298 assert.NoError(t, c.ShouldBindUri(&person))
299 assert.True(t, person.Name != "")
300 assert.True(t, person.ID != "")
301 c.String(http.StatusOK, "ShouldBindUri test OK")
302 })
303
304 path, _ := exampleFromPath("/rest/:name/:id")
305 w := PerformRequest(router, http.MethodGet, path)
306 assert.Equal(t, "ShouldBindUri test OK", w.Body.String())
307 assert.Equal(t, http.StatusOK, w.Code)
308 }
309
310 func TestBindUri(t *testing.T) {
311 DefaultWriter = os.Stdout
312 router := New()
313
314 type Person struct {
315 Name string `uri:"name" binding:"required"`
316 ID string `uri:"id" binding:"required"`
317 }
318 router.Handle(http.MethodGet, "/rest/:name/:id", func(c *Context) {
319 var person Person
320 assert.NoError(t, c.BindUri(&person))
321 assert.True(t, person.Name != "")
322 assert.True(t, person.ID != "")
323 c.String(http.StatusOK, "BindUri test OK")
324 })
325
326 path, _ := exampleFromPath("/rest/:name/:id")
327 w := PerformRequest(router, http.MethodGet, path)
328 assert.Equal(t, "BindUri test OK", w.Body.String())
329 assert.Equal(t, http.StatusOK, w.Code)
330 }
331
332 func TestBindUriError(t *testing.T) {
333 DefaultWriter = os.Stdout
334 router := New()
335
336 type Member struct {
337 Number string `uri:"num" binding:"required,uuid"`
338 }
339 router.Handle(http.MethodGet, "/new/rest/:num", func(c *Context) {
340 var m Member
341 assert.Error(t, c.BindUri(&m))
342 })
343
344 path1, _ := exampleFromPath("/new/rest/:num")
345 w1 := PerformRequest(router, http.MethodGet, path1)
346 assert.Equal(t, http.StatusBadRequest, w1.Code)
347 }
348
349 func TestRaceContextCopy(t *testing.T) {
350 DefaultWriter = os.Stdout
351 router := Default()
352 router.GET("/test/copy/race", func(c *Context) {
353 c.Set("1", 0)
354 c.Set("2", 0)
355
356
357 go readWriteKeys(c.Copy())
358 go readWriteKeys(c.Copy())
359 c.String(http.StatusOK, "run OK, no panics")
360 })
361 w := PerformRequest(router, http.MethodGet, "/test/copy/race")
362 assert.Equal(t, "run OK, no panics", w.Body.String())
363 }
364
365 func readWriteKeys(c *Context) {
366 for {
367 c.Set("1", rand.Int())
368 c.Set("2", c.Value("1"))
369 }
370 }
371
372 func githubConfigRouter(router *Engine) {
373 for _, route := range githubAPI {
374 router.Handle(route.method, route.path, func(c *Context) {
375 output := make(map[string]string, len(c.Params)+1)
376 output["status"] = "good"
377 for _, param := range c.Params {
378 output[param.Key] = param.Value
379 }
380 c.JSON(http.StatusOK, output)
381 })
382 }
383 }
384
385 func TestGithubAPI(t *testing.T) {
386 DefaultWriter = os.Stdout
387 router := New()
388 githubConfigRouter(router)
389
390 for _, route := range githubAPI {
391 path, values := exampleFromPath(route.path)
392 w := PerformRequest(router, route.method, path)
393
394
395 assert.Contains(t, w.Body.String(), "\"status\":\"good\"")
396 for _, value := range values {
397 str := fmt.Sprintf("\"%s\":\"%s\"", value.Key, value.Value)
398 assert.Contains(t, w.Body.String(), str)
399 }
400 }
401 }
402
403 func exampleFromPath(path string) (string, Params) {
404 output := new(strings.Builder)
405 params := make(Params, 0, 6)
406 start := -1
407 for i, c := range path {
408 if c == ':' {
409 start = i + 1
410 }
411 if start >= 0 {
412 if c == '/' {
413 value := fmt.Sprint(rand.Intn(100000))
414 params = append(params, Param{
415 Key: path[start:i],
416 Value: value,
417 })
418 output.WriteString(value)
419 output.WriteRune(c)
420 start = -1
421 }
422 } else {
423 output.WriteRune(c)
424 }
425 }
426 if start >= 0 {
427 value := fmt.Sprint(rand.Intn(100000))
428 params = append(params, Param{
429 Key: path[start:],
430 Value: value,
431 })
432 output.WriteString(value)
433 }
434
435 return output.String(), params
436 }
437
438 func BenchmarkGithub(b *testing.B) {
439 router := New()
440 githubConfigRouter(router)
441 runRequest(b, router, http.MethodGet, "/legacy/issues/search/:owner/:repository/:state/:keyword")
442 }
443
444 func BenchmarkParallelGithub(b *testing.B) {
445 DefaultWriter = os.Stdout
446 router := New()
447 githubConfigRouter(router)
448
449 req, _ := http.NewRequest(http.MethodPost, "/repos/manucorporat/sse/git/blobs", nil)
450
451 b.RunParallel(func(pb *testing.PB) {
452
453 for pb.Next() {
454 w := httptest.NewRecorder()
455 router.ServeHTTP(w, req)
456 }
457 })
458 }
459
460 func BenchmarkParallelGithubDefault(b *testing.B) {
461 DefaultWriter = os.Stdout
462 router := New()
463 githubConfigRouter(router)
464
465 req, _ := http.NewRequest(http.MethodPost, "/repos/manucorporat/sse/git/blobs", nil)
466
467 b.RunParallel(func(pb *testing.PB) {
468
469 for pb.Next() {
470 w := httptest.NewRecorder()
471 router.ServeHTTP(w, req)
472 }
473 })
474 }
475
View as plain text