gitea源码

api_repo_files_change_test.go 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. // Copyright 2023 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package integration
  4. import (
  5. "encoding/base64"
  6. "fmt"
  7. "net/http"
  8. "net/url"
  9. "testing"
  10. auth_model "code.gitea.io/gitea/models/auth"
  11. repo_model "code.gitea.io/gitea/models/repo"
  12. "code.gitea.io/gitea/models/unittest"
  13. user_model "code.gitea.io/gitea/models/user"
  14. "code.gitea.io/gitea/modules/gitrepo"
  15. "code.gitea.io/gitea/modules/setting"
  16. api "code.gitea.io/gitea/modules/structs"
  17. "code.gitea.io/gitea/services/context"
  18. "github.com/stretchr/testify/assert"
  19. )
  20. func getChangeFilesOptions() *api.ChangeFilesOptions {
  21. newContent := "This is new text"
  22. updateContent := "This is updated text"
  23. newContentEncoded := base64.StdEncoding.EncodeToString([]byte(newContent))
  24. updateContentEncoded := base64.StdEncoding.EncodeToString([]byte(updateContent))
  25. return &api.ChangeFilesOptions{
  26. FileOptions: api.FileOptions{
  27. BranchName: "master",
  28. NewBranchName: "master",
  29. Message: "My update of new/file.txt",
  30. Author: api.Identity{
  31. Name: "Anne Doe",
  32. Email: "annedoe@example.com",
  33. },
  34. Committer: api.Identity{
  35. Name: "John Doe",
  36. Email: "johndoe@example.com",
  37. },
  38. },
  39. Files: []*api.ChangeFileOperation{
  40. {
  41. Operation: "create",
  42. ContentBase64: newContentEncoded,
  43. },
  44. {
  45. Operation: "update",
  46. ContentBase64: updateContentEncoded,
  47. SHA: "103ff9234cefeee5ec5361d22b49fbb04d385885",
  48. },
  49. {
  50. Operation: "delete",
  51. SHA: "103ff9234cefeee5ec5361d22b49fbb04d385885",
  52. },
  53. },
  54. }
  55. }
  56. func TestAPIChangeFiles(t *testing.T) {
  57. onGiteaRun(t, func(t *testing.T, u *url.URL) {
  58. user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // owner of the repo1 & repo16
  59. org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) // owner of the repo3, is an org
  60. user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) // owner of neither repos
  61. repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) // public repo
  62. repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) // public repo
  63. repo16 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 16}) // private repo
  64. fileID := 0
  65. // Get user2's token
  66. session := loginUser(t, user2.Name)
  67. token2 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
  68. // Get user4's token
  69. session = loginUser(t, user4.Name)
  70. token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
  71. // Test changing files in repo1 which user2 owns, try both with branch and empty branch
  72. for _, branch := range []string{"master", ""} {
  73. t.Run("Branch-"+branch, func(t *testing.T) {
  74. fileID++
  75. createTreePath := fmt.Sprintf("new/file%d.txt", fileID)
  76. updateTreePath := fmt.Sprintf("update/file%d.txt", fileID)
  77. deleteTreePath := fmt.Sprintf("delete/file%d.txt", fileID)
  78. _, _ = createFile(user2, repo1, updateTreePath)
  79. _, _ = createFile(user2, repo1, deleteTreePath)
  80. changeFilesOptions := getChangeFilesOptions()
  81. changeFilesOptions.BranchName = branch
  82. changeFilesOptions.Files[0].Path = createTreePath
  83. changeFilesOptions.Files[1].Path = updateTreePath
  84. changeFilesOptions.Files[2].Path = deleteTreePath
  85. req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents", user2.Name, repo1.Name), &changeFilesOptions).
  86. AddTokenAuth(token2)
  87. resp := MakeRequest(t, req, http.StatusCreated)
  88. gitRepo, _ := gitrepo.OpenRepository(t.Context(), repo1)
  89. defer gitRepo.Close()
  90. commitID, _ := gitRepo.GetBranchCommitID(changeFilesOptions.NewBranchName)
  91. createLasCommit, _ := gitRepo.GetCommitByPath(createTreePath)
  92. updateLastCommit, _ := gitRepo.GetCommitByPath(updateTreePath)
  93. expectedCreateFileResponse := getExpectedFileResponseForCreate(apiFileResponseInfo{
  94. repoFullName: fmt.Sprintf("%s/%s", user2.Name, repo1.Name),
  95. commitID: commitID,
  96. treePath: createTreePath,
  97. lastCommitSHA: createLasCommit.ID.String(),
  98. lastCommitterWhen: createLasCommit.Committer.When,
  99. lastAuthorWhen: createLasCommit.Author.When,
  100. })
  101. expectedUpdateFileResponse := getExpectedFileResponseForUpdate(apiFileResponseInfo{
  102. commitID: commitID,
  103. treePath: updateTreePath,
  104. lastCommitSHA: updateLastCommit.ID.String(),
  105. lastCommitterWhen: updateLastCommit.Committer.When,
  106. lastAuthorWhen: updateLastCommit.Author.When,
  107. })
  108. var filesResponse api.FilesResponse
  109. DecodeJSON(t, resp, &filesResponse)
  110. normalizeFileContentResponseCommitTime(filesResponse.Files[0])
  111. normalizeFileContentResponseCommitTime(filesResponse.Files[1])
  112. assert.Equal(t, expectedCreateFileResponse.Content, filesResponse.Files[0]) // check create file
  113. assert.Equal(t, expectedUpdateFileResponse.Content, filesResponse.Files[1]) // check update file
  114. assert.Equal(t, expectedCreateFileResponse.Commit.SHA, filesResponse.Commit.SHA)
  115. assert.Equal(t, expectedCreateFileResponse.Commit.HTMLURL, filesResponse.Commit.HTMLURL)
  116. assert.Equal(t, expectedCreateFileResponse.Commit.Author.Email, filesResponse.Commit.Author.Email)
  117. assert.Equal(t, expectedCreateFileResponse.Commit.Author.Name, filesResponse.Commit.Author.Name)
  118. assert.Equal(t, expectedCreateFileResponse.Commit.Committer.Email, filesResponse.Commit.Committer.Email)
  119. assert.Equal(t, expectedCreateFileResponse.Commit.Committer.Name, filesResponse.Commit.Committer.Name)
  120. assert.Nil(t, filesResponse.Files[2]) // test delete file
  121. })
  122. }
  123. // Test changing files in a new branch
  124. changeFilesOptions := getChangeFilesOptions()
  125. changeFilesOptions.BranchName = repo1.DefaultBranch
  126. changeFilesOptions.NewBranchName = "new_branch"
  127. fileID++
  128. createTreePath := fmt.Sprintf("new/file%d.txt", fileID)
  129. updateTreePath := fmt.Sprintf("update/file%d.txt", fileID)
  130. deleteTreePath := fmt.Sprintf("delete/file%d.txt", fileID)
  131. changeFilesOptions.Files[0].Path = createTreePath
  132. changeFilesOptions.Files[1].Path = updateTreePath
  133. changeFilesOptions.Files[2].Path = deleteTreePath
  134. createFile(user2, repo1, updateTreePath)
  135. createFile(user2, repo1, deleteTreePath)
  136. url := fmt.Sprintf("/api/v1/repos/%s/%s/contents", user2.Name, repo1.Name)
  137. req := NewRequestWithJSON(t, "POST", url, &changeFilesOptions).
  138. AddTokenAuth(token2)
  139. resp := MakeRequest(t, req, http.StatusCreated)
  140. var filesResponse api.FilesResponse
  141. DecodeJSON(t, resp, &filesResponse)
  142. expectedCreateSHA := "a635aa942442ddfdba07468cf9661c08fbdf0ebf"
  143. expectedCreateHTMLURL := fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/new_branch/new/file%d.txt", fileID)
  144. expectedCreateDownloadURL := fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/new_branch/new/file%d.txt", fileID)
  145. expectedUpdateSHA := "08bd14b2e2852529157324de9c226b3364e76136"
  146. expectedUpdateHTMLURL := fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/new_branch/update/file%d.txt", fileID)
  147. expectedUpdateDownloadURL := fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/new_branch/update/file%d.txt", fileID)
  148. assert.Equal(t, expectedCreateSHA, filesResponse.Files[0].SHA)
  149. assert.Equal(t, expectedCreateHTMLURL, *filesResponse.Files[0].HTMLURL)
  150. assert.Equal(t, expectedCreateDownloadURL, *filesResponse.Files[0].DownloadURL)
  151. assert.Equal(t, expectedUpdateSHA, filesResponse.Files[1].SHA)
  152. assert.Equal(t, expectedUpdateHTMLURL, *filesResponse.Files[1].HTMLURL)
  153. assert.Equal(t, expectedUpdateDownloadURL, *filesResponse.Files[1].DownloadURL)
  154. assert.Nil(t, filesResponse.Files[2])
  155. assert.Equal(t, changeFilesOptions.Message+"\n", filesResponse.Commit.Message)
  156. // Test updating a file and renaming it
  157. changeFilesOptions = getChangeFilesOptions()
  158. changeFilesOptions.BranchName = repo1.DefaultBranch
  159. fileID++
  160. updateTreePath = fmt.Sprintf("update/file%d.txt", fileID)
  161. createFile(user2, repo1, updateTreePath)
  162. changeFilesOptions.Files = []*api.ChangeFileOperation{changeFilesOptions.Files[1]}
  163. changeFilesOptions.Files[0].FromPath = updateTreePath
  164. changeFilesOptions.Files[0].Path = "rename/" + updateTreePath
  165. req = NewRequestWithJSON(t, "POST", url, &changeFilesOptions).
  166. AddTokenAuth(token2)
  167. resp = MakeRequest(t, req, http.StatusCreated)
  168. DecodeJSON(t, resp, &filesResponse)
  169. expectedUpdateSHA = "08bd14b2e2852529157324de9c226b3364e76136"
  170. expectedUpdateHTMLURL = fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/master/rename/update/file%d.txt", fileID)
  171. expectedUpdateDownloadURL = fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/master/rename/update/file%d.txt", fileID)
  172. assert.Equal(t, expectedUpdateSHA, filesResponse.Files[0].SHA)
  173. assert.Equal(t, expectedUpdateHTMLURL, *filesResponse.Files[0].HTMLURL)
  174. assert.Equal(t, expectedUpdateDownloadURL, *filesResponse.Files[0].DownloadURL)
  175. // Test updating a file without a message
  176. changeFilesOptions = getChangeFilesOptions()
  177. changeFilesOptions.Message = ""
  178. changeFilesOptions.BranchName = repo1.DefaultBranch
  179. fileID++
  180. createTreePath = fmt.Sprintf("new/file%d.txt", fileID)
  181. updateTreePath = fmt.Sprintf("update/file%d.txt", fileID)
  182. deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID)
  183. changeFilesOptions.Files[0].Path = createTreePath
  184. changeFilesOptions.Files[1].Path = updateTreePath
  185. changeFilesOptions.Files[2].Path = deleteTreePath
  186. createFile(user2, repo1, updateTreePath)
  187. createFile(user2, repo1, deleteTreePath)
  188. req = NewRequestWithJSON(t, "POST", url, &changeFilesOptions).
  189. AddTokenAuth(token2)
  190. resp = MakeRequest(t, req, http.StatusCreated)
  191. DecodeJSON(t, resp, &filesResponse)
  192. expectedMessage := fmt.Sprintf("Add %v\nUpdate %v\nDelete %v\n", createTreePath, updateTreePath, deleteTreePath)
  193. assert.Equal(t, expectedMessage, filesResponse.Commit.Message)
  194. // Test updating a file with the wrong SHA
  195. fileID++
  196. updateTreePath = fmt.Sprintf("update/file%d.txt", fileID)
  197. createFile(user2, repo1, updateTreePath)
  198. changeFilesOptions = getChangeFilesOptions()
  199. changeFilesOptions.Files = []*api.ChangeFileOperation{changeFilesOptions.Files[1]}
  200. changeFilesOptions.Files[0].Path = updateTreePath
  201. correctSHA := changeFilesOptions.Files[0].SHA
  202. changeFilesOptions.Files[0].SHA = "badsha"
  203. req = NewRequestWithJSON(t, "POST", url, &changeFilesOptions).
  204. AddTokenAuth(token2)
  205. resp = MakeRequest(t, req, http.StatusUnprocessableEntity)
  206. expectedAPIError := context.APIError{
  207. Message: "sha does not match [given: " + changeFilesOptions.Files[0].SHA + ", expected: " + correctSHA + "]",
  208. URL: setting.API.SwaggerURL,
  209. }
  210. var apiError context.APIError
  211. DecodeJSON(t, resp, &apiError)
  212. assert.Equal(t, expectedAPIError, apiError)
  213. // Test creating a file in repo1 by user4 who does not have write access
  214. fileID++
  215. createTreePath = fmt.Sprintf("new/file%d.txt", fileID)
  216. updateTreePath = fmt.Sprintf("update/file%d.txt", fileID)
  217. deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID)
  218. createFile(user2, repo16, updateTreePath)
  219. createFile(user2, repo16, deleteTreePath)
  220. changeFilesOptions = getChangeFilesOptions()
  221. changeFilesOptions.Files[0].Path = createTreePath
  222. changeFilesOptions.Files[1].Path = updateTreePath
  223. changeFilesOptions.Files[2].Path = deleteTreePath
  224. req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents", user2.Name, repo16.Name), &changeFilesOptions).
  225. AddTokenAuth(token4)
  226. MakeRequest(t, req, http.StatusNotFound)
  227. // Tests a repo with no token given so will fail
  228. fileID++
  229. createTreePath = fmt.Sprintf("new/file%d.txt", fileID)
  230. updateTreePath = fmt.Sprintf("update/file%d.txt", fileID)
  231. deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID)
  232. createFile(user2, repo16, updateTreePath)
  233. createFile(user2, repo16, deleteTreePath)
  234. changeFilesOptions = getChangeFilesOptions()
  235. changeFilesOptions.Files[0].Path = createTreePath
  236. changeFilesOptions.Files[1].Path = updateTreePath
  237. changeFilesOptions.Files[2].Path = deleteTreePath
  238. req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents", user2.Name, repo16.Name), &changeFilesOptions)
  239. MakeRequest(t, req, http.StatusNotFound)
  240. // Test using access token for a private repo that the user of the token owns
  241. fileID++
  242. createTreePath = fmt.Sprintf("new/file%d.txt", fileID)
  243. updateTreePath = fmt.Sprintf("update/file%d.txt", fileID)
  244. deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID)
  245. createFile(user2, repo16, updateTreePath)
  246. createFile(user2, repo16, deleteTreePath)
  247. changeFilesOptions = getChangeFilesOptions()
  248. changeFilesOptions.Files[0].Path = createTreePath
  249. changeFilesOptions.Files[1].Path = updateTreePath
  250. changeFilesOptions.Files[2].Path = deleteTreePath
  251. req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents", user2.Name, repo16.Name), &changeFilesOptions).
  252. AddTokenAuth(token2)
  253. MakeRequest(t, req, http.StatusCreated)
  254. // Test using org repo "org3/repo3" where user2 is a collaborator
  255. fileID++
  256. createTreePath = fmt.Sprintf("new/file%d.txt", fileID)
  257. updateTreePath = fmt.Sprintf("update/file%d.txt", fileID)
  258. deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID)
  259. createFile(org3, repo3, updateTreePath)
  260. createFile(org3, repo3, deleteTreePath)
  261. changeFilesOptions = getChangeFilesOptions()
  262. changeFilesOptions.Files[0].Path = createTreePath
  263. changeFilesOptions.Files[1].Path = updateTreePath
  264. changeFilesOptions.Files[2].Path = deleteTreePath
  265. req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents", org3.Name, repo3.Name), &changeFilesOptions).
  266. AddTokenAuth(token2)
  267. MakeRequest(t, req, http.StatusCreated)
  268. // Test using org repo "org3/repo3" with no user token
  269. fileID++
  270. createTreePath = fmt.Sprintf("new/file%d.txt", fileID)
  271. updateTreePath = fmt.Sprintf("update/file%d.txt", fileID)
  272. deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID)
  273. createFile(org3, repo3, updateTreePath)
  274. createFile(org3, repo3, deleteTreePath)
  275. changeFilesOptions = getChangeFilesOptions()
  276. changeFilesOptions.Files[0].Path = createTreePath
  277. changeFilesOptions.Files[1].Path = updateTreePath
  278. changeFilesOptions.Files[2].Path = deleteTreePath
  279. req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents", org3.Name, repo3.Name), &changeFilesOptions)
  280. MakeRequest(t, req, http.StatusNotFound)
  281. // Test using repo "user2/repo1" where user4 is a NOT collaborator
  282. fileID++
  283. createTreePath = fmt.Sprintf("new/file%d.txt", fileID)
  284. updateTreePath = fmt.Sprintf("update/file%d.txt", fileID)
  285. deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID)
  286. createFile(user2, repo1, updateTreePath)
  287. createFile(user2, repo1, deleteTreePath)
  288. changeFilesOptions = getChangeFilesOptions()
  289. changeFilesOptions.Files[0].Path = createTreePath
  290. changeFilesOptions.Files[1].Path = updateTreePath
  291. changeFilesOptions.Files[2].Path = deleteTreePath
  292. req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents", user2.Name, repo1.Name), &changeFilesOptions).
  293. AddTokenAuth(token4)
  294. MakeRequest(t, req, http.StatusForbidden)
  295. })
  296. }