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_repo_files_change_test.go 14KB

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