githubapi_test.go 15 KB

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