You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

api_helper_for_declarative_test.go 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. // Copyright 2019 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package integration
  4. import (
  5. "context"
  6. "fmt"
  7. "net/http"
  8. "net/http/httptest"
  9. "net/url"
  10. "os"
  11. "testing"
  12. "time"
  13. "code.gitea.io/gitea/models/auth"
  14. "code.gitea.io/gitea/models/perm"
  15. repo_model "code.gitea.io/gitea/models/repo"
  16. "code.gitea.io/gitea/modules/json"
  17. "code.gitea.io/gitea/modules/queue"
  18. api "code.gitea.io/gitea/modules/structs"
  19. "code.gitea.io/gitea/services/forms"
  20. "github.com/stretchr/testify/assert"
  21. )
  22. type APITestContext struct {
  23. Reponame string
  24. Session *TestSession
  25. Token string
  26. Username string
  27. ExpectedCode int
  28. }
  29. func NewAPITestContext(t *testing.T, username, reponame string, scope ...auth.AccessTokenScope) APITestContext {
  30. session := loginUser(t, username)
  31. token := getTokenForLoggedInUser(t, session, scope...)
  32. return APITestContext{
  33. Session: session,
  34. Token: token,
  35. Username: username,
  36. Reponame: reponame,
  37. }
  38. }
  39. func (ctx APITestContext) GitPath() string {
  40. return fmt.Sprintf("%s/%s.git", ctx.Username, ctx.Reponame)
  41. }
  42. func doAPICreateRepository(ctx APITestContext, empty bool, callback ...func(*testing.T, api.Repository)) func(*testing.T) {
  43. return func(t *testing.T) {
  44. createRepoOption := &api.CreateRepoOption{
  45. AutoInit: !empty,
  46. Description: "Temporary repo",
  47. Name: ctx.Reponame,
  48. Private: true,
  49. Template: true,
  50. Gitignores: "",
  51. License: "WTFPL",
  52. Readme: "Default",
  53. }
  54. req := NewRequestWithJSON(t, "POST", "/api/v1/user/repos", createRepoOption).
  55. AddTokenAuth(ctx.Token)
  56. if ctx.ExpectedCode != 0 {
  57. ctx.Session.MakeRequest(t, req, ctx.ExpectedCode)
  58. return
  59. }
  60. resp := ctx.Session.MakeRequest(t, req, http.StatusCreated)
  61. var repository api.Repository
  62. DecodeJSON(t, resp, &repository)
  63. if len(callback) > 0 {
  64. callback[0](t, repository)
  65. }
  66. }
  67. }
  68. func doAPIEditRepository(ctx APITestContext, editRepoOption *api.EditRepoOption, callback ...func(*testing.T, api.Repository)) func(*testing.T) {
  69. return func(t *testing.T) {
  70. req := NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/repos/%s/%s", url.PathEscape(ctx.Username), url.PathEscape(ctx.Reponame)), editRepoOption).
  71. AddTokenAuth(ctx.Token)
  72. if ctx.ExpectedCode != 0 {
  73. ctx.Session.MakeRequest(t, req, ctx.ExpectedCode)
  74. return
  75. }
  76. resp := ctx.Session.MakeRequest(t, req, http.StatusOK)
  77. var repository api.Repository
  78. DecodeJSON(t, resp, &repository)
  79. if len(callback) > 0 {
  80. callback[0](t, repository)
  81. }
  82. }
  83. }
  84. func doAPIAddCollaborator(ctx APITestContext, username string, mode perm.AccessMode) func(*testing.T) {
  85. return func(t *testing.T) {
  86. permission := "read"
  87. if mode == perm.AccessModeAdmin {
  88. permission = "admin"
  89. } else if mode > perm.AccessModeRead {
  90. permission = "write"
  91. }
  92. addCollaboratorOption := &api.AddCollaboratorOption{
  93. Permission: &permission,
  94. }
  95. req := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/repos/%s/%s/collaborators/%s", ctx.Username, ctx.Reponame, username), addCollaboratorOption).
  96. AddTokenAuth(ctx.Token)
  97. if ctx.ExpectedCode != 0 {
  98. ctx.Session.MakeRequest(t, req, ctx.ExpectedCode)
  99. return
  100. }
  101. ctx.Session.MakeRequest(t, req, http.StatusNoContent)
  102. }
  103. }
  104. func doAPIForkRepository(ctx APITestContext, username string, callback ...func(*testing.T, api.Repository)) func(*testing.T) {
  105. return func(t *testing.T) {
  106. createForkOption := &api.CreateForkOption{}
  107. req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/forks", username, ctx.Reponame), createForkOption).
  108. AddTokenAuth(ctx.Token)
  109. if ctx.ExpectedCode != 0 {
  110. ctx.Session.MakeRequest(t, req, ctx.ExpectedCode)
  111. return
  112. }
  113. resp := ctx.Session.MakeRequest(t, req, http.StatusAccepted)
  114. var repository api.Repository
  115. DecodeJSON(t, resp, &repository)
  116. if len(callback) > 0 {
  117. callback[0](t, repository)
  118. }
  119. }
  120. }
  121. func doAPIGetRepository(ctx APITestContext, callback ...func(*testing.T, api.Repository)) func(*testing.T) {
  122. return func(t *testing.T) {
  123. req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s", ctx.Username, ctx.Reponame)).
  124. AddTokenAuth(ctx.Token)
  125. if ctx.ExpectedCode != 0 {
  126. ctx.Session.MakeRequest(t, req, ctx.ExpectedCode)
  127. return
  128. }
  129. resp := ctx.Session.MakeRequest(t, req, http.StatusOK)
  130. var repository api.Repository
  131. DecodeJSON(t, resp, &repository)
  132. if len(callback) > 0 {
  133. callback[0](t, repository)
  134. }
  135. }
  136. }
  137. func doAPIDeleteRepository(ctx APITestContext) func(*testing.T) {
  138. return func(t *testing.T) {
  139. req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/%s/%s", ctx.Username, ctx.Reponame)).
  140. AddTokenAuth(ctx.Token)
  141. if ctx.ExpectedCode != 0 {
  142. ctx.Session.MakeRequest(t, req, ctx.ExpectedCode)
  143. return
  144. }
  145. ctx.Session.MakeRequest(t, req, http.StatusNoContent)
  146. }
  147. }
  148. func doAPICreateUserKey(ctx APITestContext, keyname, keyFile string, callback ...func(*testing.T, api.PublicKey)) func(*testing.T) {
  149. return func(t *testing.T) {
  150. dataPubKey, err := os.ReadFile(keyFile + ".pub")
  151. assert.NoError(t, err)
  152. req := NewRequestWithJSON(t, "POST", "/api/v1/user/keys", &api.CreateKeyOption{
  153. Title: keyname,
  154. Key: string(dataPubKey),
  155. }).AddTokenAuth(ctx.Token)
  156. if ctx.ExpectedCode != 0 {
  157. ctx.Session.MakeRequest(t, req, ctx.ExpectedCode)
  158. return
  159. }
  160. resp := ctx.Session.MakeRequest(t, req, http.StatusCreated)
  161. var publicKey api.PublicKey
  162. DecodeJSON(t, resp, &publicKey)
  163. if len(callback) > 0 {
  164. callback[0](t, publicKey)
  165. }
  166. }
  167. }
  168. func doAPIDeleteUserKey(ctx APITestContext, keyID int64) func(*testing.T) {
  169. return func(t *testing.T) {
  170. req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/user/keys/%d", keyID)).
  171. AddTokenAuth(ctx.Token)
  172. if ctx.ExpectedCode != 0 {
  173. ctx.Session.MakeRequest(t, req, ctx.ExpectedCode)
  174. return
  175. }
  176. ctx.Session.MakeRequest(t, req, http.StatusNoContent)
  177. }
  178. }
  179. func doAPICreateDeployKey(ctx APITestContext, keyname, keyFile string, readOnly bool) func(*testing.T) {
  180. return func(t *testing.T) {
  181. dataPubKey, err := os.ReadFile(keyFile + ".pub")
  182. assert.NoError(t, err)
  183. req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/keys", ctx.Username, ctx.Reponame), api.CreateKeyOption{
  184. Title: keyname,
  185. Key: string(dataPubKey),
  186. ReadOnly: readOnly,
  187. }).AddTokenAuth(ctx.Token)
  188. if ctx.ExpectedCode != 0 {
  189. ctx.Session.MakeRequest(t, req, ctx.ExpectedCode)
  190. return
  191. }
  192. ctx.Session.MakeRequest(t, req, http.StatusCreated)
  193. }
  194. }
  195. func doAPICreatePullRequest(ctx APITestContext, owner, repo, baseBranch, headBranch string) func(*testing.T) (api.PullRequest, error) {
  196. return func(t *testing.T) (api.PullRequest, error) {
  197. req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", owner, repo), &api.CreatePullRequestOption{
  198. Head: headBranch,
  199. Base: baseBranch,
  200. Title: fmt.Sprintf("create a pr from %s to %s", headBranch, baseBranch),
  201. }).AddTokenAuth(ctx.Token)
  202. expected := http.StatusCreated
  203. if ctx.ExpectedCode != 0 {
  204. expected = ctx.ExpectedCode
  205. }
  206. resp := ctx.Session.MakeRequest(t, req, expected)
  207. decoder := json.NewDecoder(resp.Body)
  208. pr := api.PullRequest{}
  209. err := decoder.Decode(&pr)
  210. return pr, err
  211. }
  212. }
  213. func doAPIGetPullRequest(ctx APITestContext, owner, repo string, index int64) func(*testing.T) (api.PullRequest, error) {
  214. return func(t *testing.T) (api.PullRequest, error) {
  215. req := NewRequest(t, http.MethodGet, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d", owner, repo, index)).
  216. AddTokenAuth(ctx.Token)
  217. expected := http.StatusOK
  218. if ctx.ExpectedCode != 0 {
  219. expected = ctx.ExpectedCode
  220. }
  221. resp := ctx.Session.MakeRequest(t, req, expected)
  222. decoder := json.NewDecoder(resp.Body)
  223. pr := api.PullRequest{}
  224. err := decoder.Decode(&pr)
  225. return pr, err
  226. }
  227. }
  228. func doAPIMergePullRequest(ctx APITestContext, owner, repo string, index int64) func(*testing.T) {
  229. return func(t *testing.T) {
  230. urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/merge", owner, repo, index)
  231. var req *RequestWrapper
  232. var resp *httptest.ResponseRecorder
  233. for i := 0; i < 6; i++ {
  234. req = NewRequestWithJSON(t, http.MethodPost, urlStr, &forms.MergePullRequestForm{
  235. MergeMessageField: "doAPIMergePullRequest Merge",
  236. Do: string(repo_model.MergeStyleMerge),
  237. }).AddTokenAuth(ctx.Token)
  238. resp = ctx.Session.MakeRequest(t, req, NoExpectedStatus)
  239. if resp.Code != http.StatusMethodNotAllowed {
  240. break
  241. }
  242. err := api.APIError{}
  243. DecodeJSON(t, resp, &err)
  244. assert.EqualValues(t, "Please try again later", err.Message)
  245. queue.GetManager().FlushAll(context.Background(), 5*time.Second)
  246. <-time.After(1 * time.Second)
  247. }
  248. expected := ctx.ExpectedCode
  249. if expected == 0 {
  250. expected = http.StatusOK
  251. }
  252. if !assert.EqualValues(t, expected, resp.Code,
  253. "Request: %s %s", req.Method, req.URL.String()) {
  254. logUnexpectedResponse(t, resp)
  255. }
  256. }
  257. }
  258. func doAPIManuallyMergePullRequest(ctx APITestContext, owner, repo, commitID string, index int64) func(*testing.T) {
  259. return func(t *testing.T) {
  260. urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/merge", owner, repo, index)
  261. req := NewRequestWithJSON(t, http.MethodPost, urlStr, &forms.MergePullRequestForm{
  262. Do: string(repo_model.MergeStyleManuallyMerged),
  263. MergeCommitID: commitID,
  264. }).AddTokenAuth(ctx.Token)
  265. if ctx.ExpectedCode != 0 {
  266. ctx.Session.MakeRequest(t, req, ctx.ExpectedCode)
  267. return
  268. }
  269. ctx.Session.MakeRequest(t, req, http.StatusOK)
  270. }
  271. }
  272. func doAPIAutoMergePullRequest(ctx APITestContext, owner, repo string, index int64) func(*testing.T) {
  273. return func(t *testing.T) {
  274. urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/merge", owner, repo, index)
  275. req := NewRequestWithJSON(t, http.MethodPost, urlStr, &forms.MergePullRequestForm{
  276. MergeMessageField: "doAPIMergePullRequest Merge",
  277. Do: string(repo_model.MergeStyleMerge),
  278. MergeWhenChecksSucceed: true,
  279. }).AddTokenAuth(ctx.Token)
  280. if ctx.ExpectedCode != 0 {
  281. ctx.Session.MakeRequest(t, req, ctx.ExpectedCode)
  282. return
  283. }
  284. ctx.Session.MakeRequest(t, req, http.StatusOK)
  285. }
  286. }
  287. func doAPICancelAutoMergePullRequest(ctx APITestContext, owner, repo string, index int64) func(*testing.T) {
  288. return func(t *testing.T) {
  289. req := NewRequest(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/merge", owner, repo, index)).
  290. AddTokenAuth(ctx.Token)
  291. if ctx.ExpectedCode != 0 {
  292. ctx.Session.MakeRequest(t, req, ctx.ExpectedCode)
  293. return
  294. }
  295. ctx.Session.MakeRequest(t, req, http.StatusNoContent)
  296. }
  297. }
  298. func doAPIGetBranch(ctx APITestContext, branch string, callback ...func(*testing.T, api.Branch)) func(*testing.T) {
  299. return func(t *testing.T) {
  300. req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/branches/%s", ctx.Username, ctx.Reponame, branch).
  301. AddTokenAuth(ctx.Token)
  302. if ctx.ExpectedCode != 0 {
  303. ctx.Session.MakeRequest(t, req, ctx.ExpectedCode)
  304. return
  305. }
  306. resp := ctx.Session.MakeRequest(t, req, http.StatusOK)
  307. var branch api.Branch
  308. DecodeJSON(t, resp, &branch)
  309. if len(callback) > 0 {
  310. callback[0](t, branch)
  311. }
  312. }
  313. }
  314. func doAPICreateFile(ctx APITestContext, treepath string, options *api.CreateFileOptions, callback ...func(*testing.T, api.FileResponse)) func(*testing.T) {
  315. return func(t *testing.T) {
  316. req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", ctx.Username, ctx.Reponame, treepath), &options).
  317. AddTokenAuth(ctx.Token)
  318. if ctx.ExpectedCode != 0 {
  319. ctx.Session.MakeRequest(t, req, ctx.ExpectedCode)
  320. return
  321. }
  322. resp := ctx.Session.MakeRequest(t, req, http.StatusCreated)
  323. var contents api.FileResponse
  324. DecodeJSON(t, resp, &contents)
  325. if len(callback) > 0 {
  326. callback[0](t, contents)
  327. }
  328. }
  329. }
  330. func doAPICreateOrganization(ctx APITestContext, options *api.CreateOrgOption, callback ...func(*testing.T, api.Organization)) func(t *testing.T) {
  331. return func(t *testing.T) {
  332. req := NewRequestWithJSON(t, "POST", "/api/v1/orgs", &options).
  333. AddTokenAuth(ctx.Token)
  334. if ctx.ExpectedCode != 0 {
  335. ctx.Session.MakeRequest(t, req, ctx.ExpectedCode)
  336. return
  337. }
  338. resp := ctx.Session.MakeRequest(t, req, http.StatusCreated)
  339. var contents api.Organization
  340. DecodeJSON(t, resp, &contents)
  341. if len(callback) > 0 {
  342. callback[0](t, contents)
  343. }
  344. }
  345. }
  346. func doAPICreateOrganizationRepository(ctx APITestContext, orgName string, options *api.CreateRepoOption, callback ...func(*testing.T, api.Repository)) func(t *testing.T) {
  347. return func(t *testing.T) {
  348. req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/orgs/%s/repos", orgName), &options).
  349. AddTokenAuth(ctx.Token)
  350. if ctx.ExpectedCode != 0 {
  351. ctx.Session.MakeRequest(t, req, ctx.ExpectedCode)
  352. return
  353. }
  354. resp := ctx.Session.MakeRequest(t, req, http.StatusCreated)
  355. var contents api.Repository
  356. DecodeJSON(t, resp, &contents)
  357. if len(callback) > 0 {
  358. callback[0](t, contents)
  359. }
  360. }
  361. }
  362. func doAPICreateOrganizationTeam(ctx APITestContext, orgName string, options *api.CreateTeamOption, callback ...func(*testing.T, api.Team)) func(t *testing.T) {
  363. return func(t *testing.T) {
  364. req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/orgs/%s/teams", orgName), &options).
  365. AddTokenAuth(ctx.Token)
  366. if ctx.ExpectedCode != 0 {
  367. ctx.Session.MakeRequest(t, req, ctx.ExpectedCode)
  368. return
  369. }
  370. resp := ctx.Session.MakeRequest(t, req, http.StatusCreated)
  371. var contents api.Team
  372. DecodeJSON(t, resp, &contents)
  373. if len(callback) > 0 {
  374. callback[0](t, contents)
  375. }
  376. }
  377. }
  378. func doAPIAddUserToOrganizationTeam(ctx APITestContext, teamID int64, username string) func(t *testing.T) {
  379. return func(t *testing.T) {
  380. req := NewRequest(t, "PUT", fmt.Sprintf("/api/v1/teams/%d/members/%s", teamID, username)).
  381. AddTokenAuth(ctx.Token)
  382. if ctx.ExpectedCode != 0 {
  383. ctx.Session.MakeRequest(t, req, ctx.ExpectedCode)
  384. return
  385. }
  386. ctx.Session.MakeRequest(t, req, http.StatusNoContent)
  387. }
  388. }
  389. func doAPIAddRepoToOrganizationTeam(ctx APITestContext, teamID int64, orgName, repoName string) func(t *testing.T) {
  390. return func(t *testing.T) {
  391. req := NewRequest(t, "PUT", fmt.Sprintf("/api/v1/teams/%d/repos/%s/%s", teamID, orgName, repoName)).
  392. AddTokenAuth(ctx.Token)
  393. if ctx.ExpectedCode != 0 {
  394. ctx.Session.MakeRequest(t, req, ctx.ExpectedCode)
  395. return
  396. }
  397. ctx.Session.MakeRequest(t, req, http.StatusNoContent)
  398. }
  399. }