githubapi_test.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  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. "testing"
  12. "github.com/stretchr/testify/assert"
  13. )
  14. type route struct {
  15. method string
  16. path string
  17. }
  18. // http://developer.github.com/v3/
  19. var githubAPI = []route{
  20. // OAuth Authorizations
  21. {"GET", "/authorizations"},
  22. {"GET", "/authorizations/:id"},
  23. {"POST", "/authorizations"},
  24. //{"PUT", "/authorizations/clients/:client_id"},
  25. //{"PATCH", "/authorizations/:id"},
  26. {"DELETE", "/authorizations/:id"},
  27. {"GET", "/applications/:client_id/tokens/:access_token"},
  28. {"DELETE", "/applications/:client_id/tokens"},
  29. {"DELETE", "/applications/:client_id/tokens/:access_token"},
  30. // Activity
  31. {"GET", "/events"},
  32. {"GET", "/repos/:owner/:repo/events"},
  33. {"GET", "/networks/:owner/:repo/events"},
  34. {"GET", "/orgs/:org/events"},
  35. {"GET", "/users/:user/received_events"},
  36. {"GET", "/users/:user/received_events/public"},
  37. {"GET", "/users/:user/events"},
  38. {"GET", "/users/:user/events/public"},
  39. {"GET", "/users/:user/events/orgs/:org"},
  40. {"GET", "/feeds"},
  41. {"GET", "/notifications"},
  42. {"GET", "/repos/:owner/:repo/notifications"},
  43. {"PUT", "/notifications"},
  44. {"PUT", "/repos/:owner/:repo/notifications"},
  45. {"GET", "/notifications/threads/:id"},
  46. //{"PATCH", "/notifications/threads/:id"},
  47. {"GET", "/notifications/threads/:id/subscription"},
  48. {"PUT", "/notifications/threads/:id/subscription"},
  49. {"DELETE", "/notifications/threads/:id/subscription"},
  50. {"GET", "/repos/:owner/:repo/stargazers"},
  51. {"GET", "/users/:user/starred"},
  52. {"GET", "/user/starred"},
  53. {"GET", "/user/starred/:owner/:repo"},
  54. {"PUT", "/user/starred/:owner/:repo"},
  55. {"DELETE", "/user/starred/:owner/:repo"},
  56. {"GET", "/repos/:owner/:repo/subscribers"},
  57. {"GET", "/users/:user/subscriptions"},
  58. {"GET", "/user/subscriptions"},
  59. {"GET", "/repos/:owner/:repo/subscription"},
  60. {"PUT", "/repos/:owner/:repo/subscription"},
  61. {"DELETE", "/repos/:owner/:repo/subscription"},
  62. {"GET", "/user/subscriptions/:owner/:repo"},
  63. {"PUT", "/user/subscriptions/:owner/:repo"},
  64. {"DELETE", "/user/subscriptions/:owner/:repo"},
  65. // Gists
  66. {"GET", "/users/:user/gists"},
  67. {"GET", "/gists"},
  68. //{"GET", "/gists/public"},
  69. //{"GET", "/gists/starred"},
  70. {"GET", "/gists/:id"},
  71. {"POST", "/gists"},
  72. //{"PATCH", "/gists/:id"},
  73. {"PUT", "/gists/:id/star"},
  74. {"DELETE", "/gists/:id/star"},
  75. {"GET", "/gists/:id/star"},
  76. {"POST", "/gists/:id/forks"},
  77. {"DELETE", "/gists/:id"},
  78. // Git Data
  79. {"GET", "/repos/:owner/:repo/git/blobs/:sha"},
  80. {"POST", "/repos/:owner/:repo/git/blobs"},
  81. {"GET", "/repos/:owner/:repo/git/commits/:sha"},
  82. {"POST", "/repos/:owner/:repo/git/commits"},
  83. //{"GET", "/repos/:owner/:repo/git/refs/*ref"},
  84. {"GET", "/repos/:owner/:repo/git/refs"},
  85. {"POST", "/repos/:owner/:repo/git/refs"},
  86. //{"PATCH", "/repos/:owner/:repo/git/refs/*ref"},
  87. //{"DELETE", "/repos/:owner/:repo/git/refs/*ref"},
  88. {"GET", "/repos/:owner/:repo/git/tags/:sha"},
  89. {"POST", "/repos/:owner/:repo/git/tags"},
  90. {"GET", "/repos/:owner/:repo/git/trees/:sha"},
  91. {"POST", "/repos/:owner/:repo/git/trees"},
  92. // Issues
  93. {"GET", "/issues"},
  94. {"GET", "/user/issues"},
  95. {"GET", "/orgs/:org/issues"},
  96. {"GET", "/repos/:owner/:repo/issues"},
  97. {"GET", "/repos/:owner/:repo/issues/:number"},
  98. {"POST", "/repos/:owner/:repo/issues"},
  99. //{"PATCH", "/repos/:owner/:repo/issues/:number"},
  100. {"GET", "/repos/:owner/:repo/assignees"},
  101. {"GET", "/repos/:owner/:repo/assignees/:assignee"},
  102. {"GET", "/repos/:owner/:repo/issues/:number/comments"},
  103. //{"GET", "/repos/:owner/:repo/issues/comments"},
  104. //{"GET", "/repos/:owner/:repo/issues/comments/:id"},
  105. {"POST", "/repos/:owner/:repo/issues/:number/comments"},
  106. //{"PATCH", "/repos/:owner/:repo/issues/comments/:id"},
  107. //{"DELETE", "/repos/:owner/:repo/issues/comments/:id"},
  108. {"GET", "/repos/:owner/:repo/issues/:number/events"},
  109. //{"GET", "/repos/:owner/:repo/issues/events"},
  110. //{"GET", "/repos/:owner/:repo/issues/events/:id"},
  111. {"GET", "/repos/:owner/:repo/labels"},
  112. {"GET", "/repos/:owner/:repo/labels/:name"},
  113. {"POST", "/repos/:owner/:repo/labels"},
  114. //{"PATCH", "/repos/:owner/:repo/labels/:name"},
  115. {"DELETE", "/repos/:owner/:repo/labels/:name"},
  116. {"GET", "/repos/:owner/:repo/issues/:number/labels"},
  117. {"POST", "/repos/:owner/:repo/issues/:number/labels"},
  118. {"DELETE", "/repos/:owner/:repo/issues/:number/labels/:name"},
  119. {"PUT", "/repos/:owner/:repo/issues/:number/labels"},
  120. {"DELETE", "/repos/:owner/:repo/issues/:number/labels"},
  121. {"GET", "/repos/:owner/:repo/milestones/:number/labels"},
  122. {"GET", "/repos/:owner/:repo/milestones"},
  123. {"GET", "/repos/:owner/:repo/milestones/:number"},
  124. {"POST", "/repos/:owner/:repo/milestones"},
  125. //{"PATCH", "/repos/:owner/:repo/milestones/:number"},
  126. {"DELETE", "/repos/:owner/:repo/milestones/:number"},
  127. // Miscellaneous
  128. {"GET", "/emojis"},
  129. {"GET", "/gitignore/templates"},
  130. {"GET", "/gitignore/templates/:name"},
  131. {"POST", "/markdown"},
  132. {"POST", "/markdown/raw"},
  133. {"GET", "/meta"},
  134. {"GET", "/rate_limit"},
  135. // Organizations
  136. {"GET", "/users/:user/orgs"},
  137. {"GET", "/user/orgs"},
  138. {"GET", "/orgs/:org"},
  139. //{"PATCH", "/orgs/:org"},
  140. {"GET", "/orgs/:org/members"},
  141. {"GET", "/orgs/:org/members/:user"},
  142. {"DELETE", "/orgs/:org/members/:user"},
  143. {"GET", "/orgs/:org/public_members"},
  144. {"GET", "/orgs/:org/public_members/:user"},
  145. {"PUT", "/orgs/:org/public_members/:user"},
  146. {"DELETE", "/orgs/:org/public_members/:user"},
  147. {"GET", "/orgs/:org/teams"},
  148. {"GET", "/teams/:id"},
  149. {"POST", "/orgs/:org/teams"},
  150. //{"PATCH", "/teams/:id"},
  151. {"DELETE", "/teams/:id"},
  152. {"GET", "/teams/:id/members"},
  153. {"GET", "/teams/:id/members/:user"},
  154. {"PUT", "/teams/:id/members/:user"},
  155. {"DELETE", "/teams/:id/members/:user"},
  156. {"GET", "/teams/:id/repos"},
  157. {"GET", "/teams/:id/repos/:owner/:repo"},
  158. {"PUT", "/teams/:id/repos/:owner/:repo"},
  159. {"DELETE", "/teams/:id/repos/:owner/:repo"},
  160. {"GET", "/user/teams"},
  161. // Pull Requests
  162. {"GET", "/repos/:owner/:repo/pulls"},
  163. {"GET", "/repos/:owner/:repo/pulls/:number"},
  164. {"POST", "/repos/:owner/:repo/pulls"},
  165. //{"PATCH", "/repos/:owner/:repo/pulls/:number"},
  166. {"GET", "/repos/:owner/:repo/pulls/:number/commits"},
  167. {"GET", "/repos/:owner/:repo/pulls/:number/files"},
  168. {"GET", "/repos/:owner/:repo/pulls/:number/merge"},
  169. {"PUT", "/repos/:owner/:repo/pulls/:number/merge"},
  170. {"GET", "/repos/:owner/:repo/pulls/:number/comments"},
  171. //{"GET", "/repos/:owner/:repo/pulls/comments"},
  172. //{"GET", "/repos/:owner/:repo/pulls/comments/:number"},
  173. {"PUT", "/repos/:owner/:repo/pulls/:number/comments"},
  174. //{"PATCH", "/repos/:owner/:repo/pulls/comments/:number"},
  175. //{"DELETE", "/repos/:owner/:repo/pulls/comments/:number"},
  176. // Repositories
  177. {"GET", "/user/repos"},
  178. {"GET", "/users/:user/repos"},
  179. {"GET", "/orgs/:org/repos"},
  180. {"GET", "/repositories"},
  181. {"POST", "/user/repos"},
  182. {"POST", "/orgs/:org/repos"},
  183. {"GET", "/repos/:owner/:repo"},
  184. //{"PATCH", "/repos/:owner/:repo"},
  185. {"GET", "/repos/:owner/:repo/contributors"},
  186. {"GET", "/repos/:owner/:repo/languages"},
  187. {"GET", "/repos/:owner/:repo/teams"},
  188. {"GET", "/repos/:owner/:repo/tags"},
  189. {"GET", "/repos/:owner/:repo/branches"},
  190. {"GET", "/repos/:owner/:repo/branches/:branch"},
  191. {"DELETE", "/repos/:owner/:repo"},
  192. {"GET", "/repos/:owner/:repo/collaborators"},
  193. {"GET", "/repos/:owner/:repo/collaborators/:user"},
  194. {"PUT", "/repos/:owner/:repo/collaborators/:user"},
  195. {"DELETE", "/repos/:owner/:repo/collaborators/:user"},
  196. {"GET", "/repos/:owner/:repo/comments"},
  197. {"GET", "/repos/:owner/:repo/commits/:sha/comments"},
  198. {"POST", "/repos/:owner/:repo/commits/:sha/comments"},
  199. {"GET", "/repos/:owner/:repo/comments/:id"},
  200. //{"PATCH", "/repos/:owner/:repo/comments/:id"},
  201. {"DELETE", "/repos/:owner/:repo/comments/:id"},
  202. {"GET", "/repos/:owner/:repo/commits"},
  203. {"GET", "/repos/:owner/:repo/commits/:sha"},
  204. {"GET", "/repos/:owner/:repo/readme"},
  205. //{"GET", "/repos/:owner/:repo/contents/*path"},
  206. //{"PUT", "/repos/:owner/:repo/contents/*path"},
  207. //{"DELETE", "/repos/:owner/:repo/contents/*path"},
  208. //{"GET", "/repos/:owner/:repo/:archive_format/:ref"},
  209. {"GET", "/repos/:owner/:repo/keys"},
  210. {"GET", "/repos/:owner/:repo/keys/:id"},
  211. {"POST", "/repos/:owner/:repo/keys"},
  212. //{"PATCH", "/repos/:owner/:repo/keys/:id"},
  213. {"DELETE", "/repos/:owner/:repo/keys/:id"},
  214. {"GET", "/repos/:owner/:repo/downloads"},
  215. {"GET", "/repos/:owner/:repo/downloads/:id"},
  216. {"DELETE", "/repos/:owner/:repo/downloads/:id"},
  217. {"GET", "/repos/:owner/:repo/forks"},
  218. {"POST", "/repos/:owner/:repo/forks"},
  219. {"GET", "/repos/:owner/:repo/hooks"},
  220. {"GET", "/repos/:owner/:repo/hooks/:id"},
  221. {"POST", "/repos/:owner/:repo/hooks"},
  222. //{"PATCH", "/repos/:owner/:repo/hooks/:id"},
  223. {"POST", "/repos/:owner/:repo/hooks/:id/tests"},
  224. {"DELETE", "/repos/:owner/:repo/hooks/:id"},
  225. {"POST", "/repos/:owner/:repo/merges"},
  226. {"GET", "/repos/:owner/:repo/releases"},
  227. {"GET", "/repos/:owner/:repo/releases/:id"},
  228. {"POST", "/repos/:owner/:repo/releases"},
  229. //{"PATCH", "/repos/:owner/:repo/releases/:id"},
  230. {"DELETE", "/repos/:owner/:repo/releases/:id"},
  231. {"GET", "/repos/:owner/:repo/releases/:id/assets"},
  232. {"GET", "/repos/:owner/:repo/stats/contributors"},
  233. {"GET", "/repos/:owner/:repo/stats/commit_activity"},
  234. {"GET", "/repos/:owner/:repo/stats/code_frequency"},
  235. {"GET", "/repos/:owner/:repo/stats/participation"},
  236. {"GET", "/repos/:owner/:repo/stats/punch_card"},
  237. {"GET", "/repos/:owner/:repo/statuses/:ref"},
  238. {"POST", "/repos/:owner/:repo/statuses/:ref"},
  239. // Search
  240. {"GET", "/search/repositories"},
  241. {"GET", "/search/code"},
  242. {"GET", "/search/issues"},
  243. {"GET", "/search/users"},
  244. {"GET", "/legacy/issues/search/:owner/:repository/:state/:keyword"},
  245. {"GET", "/legacy/repos/search/:keyword"},
  246. {"GET", "/legacy/user/search/:keyword"},
  247. {"GET", "/legacy/user/email/:email"},
  248. // Users
  249. {"GET", "/users/:user"},
  250. {"GET", "/user"},
  251. //{"PATCH", "/user"},
  252. {"GET", "/users"},
  253. {"GET", "/user/emails"},
  254. {"POST", "/user/emails"},
  255. {"DELETE", "/user/emails"},
  256. {"GET", "/users/:user/followers"},
  257. {"GET", "/user/followers"},
  258. {"GET", "/users/:user/following"},
  259. {"GET", "/user/following"},
  260. {"GET", "/user/following/:user"},
  261. {"GET", "/users/:user/following/:target_user"},
  262. {"PUT", "/user/following/:user"},
  263. {"DELETE", "/user/following/:user"},
  264. {"GET", "/users/:user/keys"},
  265. {"GET", "/user/keys"},
  266. {"GET", "/user/keys/:id"},
  267. {"POST", "/user/keys"},
  268. //{"PATCH", "/user/keys/:id"},
  269. {"DELETE", "/user/keys/:id"},
  270. }
  271. func githubConfigRouter(router *Engine) {
  272. for _, route := range githubAPI {
  273. router.Handle(route.method, route.path, func(c *Context) {
  274. output := make(map[string]string, len(c.Params)+1)
  275. output["status"] = "good"
  276. for _, param := range c.Params {
  277. output[param.Key] = param.Value
  278. }
  279. c.JSON(200, output)
  280. })
  281. }
  282. }
  283. func TestGithubAPI(t *testing.T) {
  284. DefaultWriter = newMockWriter()
  285. router := Default()
  286. githubConfigRouter(router)
  287. for _, route := range githubAPI {
  288. path, values := exampleFromPath(route.path)
  289. w := performRequest(router, route.method, path)
  290. // TEST
  291. assert.Contains(t, w.Body.String(), "\"status\":\"good\"")
  292. for _, value := range values {
  293. str := fmt.Sprintf("\"%s\":\"%s\"", value.Key, value.Value)
  294. assert.Contains(t, w.Body.String(), str)
  295. }
  296. }
  297. }
  298. func exampleFromPath(path string) (string, Params) {
  299. output := new(bytes.Buffer)
  300. params := make(Params, 0, 6)
  301. start := -1
  302. for i, c := range path {
  303. if c == ':' {
  304. start = i + 1
  305. }
  306. if start >= 0 {
  307. if c == '/' {
  308. value := fmt.Sprint(rand.Intn(100000))
  309. params = append(params, Param{
  310. Key: path[start:i],
  311. Value: value,
  312. })
  313. output.WriteString(value)
  314. output.WriteRune(c)
  315. start = -1
  316. }
  317. } else {
  318. output.WriteRune(c)
  319. }
  320. }
  321. if start >= 0 {
  322. value := fmt.Sprint(rand.Intn(100000))
  323. params = append(params, Param{
  324. Key: path[start:len(path)],
  325. Value: value,
  326. })
  327. output.WriteString(value)
  328. }
  329. return output.String(), params
  330. }
  331. func BenchmarkGithub(b *testing.B) {
  332. router := New()
  333. githubConfigRouter(router)
  334. runRequest(b, router, "GET", "/legacy/issues/search/:owner/:repository/:state/:keyword")
  335. }
  336. func BenchmarkParallelGithub(b *testing.B) {
  337. DefaultWriter = newMockWriter()
  338. router := New()
  339. githubConfigRouter(router)
  340. req, _ := http.NewRequest("POST", "/repos/manucorporat/sse/git/blobs", nil)
  341. b.RunParallel(func(pb *testing.PB) {
  342. // Each goroutine has its own bytes.Buffer.
  343. for pb.Next() {
  344. w := httptest.NewRecorder()
  345. router.ServeHTTP(w, req)
  346. }
  347. })
  348. }
  349. func BenchmarkParallelGithubDefault(b *testing.B) {
  350. DefaultWriter = newMockWriter()
  351. router := Default()
  352. githubConfigRouter(router)
  353. req, _ := http.NewRequest("POST", "/repos/manucorporat/sse/git/blobs", nil)
  354. b.RunParallel(func(pb *testing.PB) {
  355. // Each goroutine has its own bytes.Buffer.
  356. for pb.Next() {
  357. w := httptest.NewRecorder()
  358. router.ServeHTTP(w, req)
  359. }
  360. })
  361. }