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_file_update_test.go 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. // Copyright 2019 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. "path/filepath"
  11. "testing"
  12. auth_model "code.gitea.io/gitea/models/auth"
  13. repo_model "code.gitea.io/gitea/models/repo"
  14. "code.gitea.io/gitea/models/unittest"
  15. user_model "code.gitea.io/gitea/models/user"
  16. "code.gitea.io/gitea/modules/context"
  17. "code.gitea.io/gitea/modules/git"
  18. "code.gitea.io/gitea/modules/setting"
  19. api "code.gitea.io/gitea/modules/structs"
  20. "github.com/stretchr/testify/assert"
  21. )
  22. func getUpdateFileOptions() *api.UpdateFileOptions {
  23. content := "This is updated text"
  24. contentEncoded := base64.StdEncoding.EncodeToString([]byte(content))
  25. return &api.UpdateFileOptions{
  26. DeleteFileOptions: api.DeleteFileOptions{
  27. FileOptions: api.FileOptions{
  28. BranchName: "master",
  29. NewBranchName: "master",
  30. Message: "My update of new/file.txt",
  31. Author: api.Identity{
  32. Name: "John Doe",
  33. Email: "johndoe@example.com",
  34. },
  35. Committer: api.Identity{
  36. Name: "Anne Doe",
  37. Email: "annedoe@example.com",
  38. },
  39. },
  40. SHA: "103ff9234cefeee5ec5361d22b49fbb04d385885",
  41. },
  42. ContentBase64: contentEncoded,
  43. }
  44. }
  45. func getExpectedFileResponseForUpdate(commitID, treePath, lastCommitSHA string) *api.FileResponse {
  46. sha := "08bd14b2e2852529157324de9c226b3364e76136"
  47. encoding := "base64"
  48. content := "VGhpcyBpcyB1cGRhdGVkIHRleHQ="
  49. selfURL := setting.AppURL + "api/v1/repos/user2/repo1/contents/" + treePath + "?ref=master"
  50. htmlURL := setting.AppURL + "user2/repo1/src/branch/master/" + treePath
  51. gitURL := setting.AppURL + "api/v1/repos/user2/repo1/git/blobs/" + sha
  52. downloadURL := setting.AppURL + "user2/repo1/raw/branch/master/" + treePath
  53. return &api.FileResponse{
  54. Content: &api.ContentsResponse{
  55. Name: filepath.Base(treePath),
  56. Path: treePath,
  57. SHA: sha,
  58. LastCommitSHA: lastCommitSHA,
  59. Type: "file",
  60. Size: 20,
  61. Encoding: &encoding,
  62. Content: &content,
  63. URL: &selfURL,
  64. HTMLURL: &htmlURL,
  65. GitURL: &gitURL,
  66. DownloadURL: &downloadURL,
  67. Links: &api.FileLinksResponse{
  68. Self: &selfURL,
  69. GitURL: &gitURL,
  70. HTMLURL: &htmlURL,
  71. },
  72. },
  73. Commit: &api.FileCommitResponse{
  74. CommitMeta: api.CommitMeta{
  75. URL: setting.AppURL + "api/v1/repos/user2/repo1/git/commits/" + commitID,
  76. SHA: commitID,
  77. },
  78. HTMLURL: setting.AppURL + "user2/repo1/commit/" + commitID,
  79. Author: &api.CommitUser{
  80. Identity: api.Identity{
  81. Name: "John Doe",
  82. Email: "johndoe@example.com",
  83. },
  84. },
  85. Committer: &api.CommitUser{
  86. Identity: api.Identity{
  87. Name: "Anne Doe",
  88. Email: "annedoe@example.com",
  89. },
  90. },
  91. Message: "My update of README.md\n",
  92. },
  93. Verification: &api.PayloadCommitVerification{
  94. Verified: false,
  95. Reason: "gpg.error.not_signed_commit",
  96. Signature: "",
  97. Payload: "",
  98. },
  99. }
  100. }
  101. func TestAPIUpdateFile(t *testing.T) {
  102. onGiteaRun(t, func(t *testing.T, u *url.URL) {
  103. user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // owner of the repo1 & repo16
  104. org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) // owner of the repo3, is an org
  105. user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) // owner of neither repos
  106. repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) // public repo
  107. repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) // public repo
  108. repo16 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 16}) // private repo
  109. fileID := 0
  110. // Get user2's token
  111. session := loginUser(t, user2.Name)
  112. token2 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
  113. // Get user4's token
  114. session = loginUser(t, user4.Name)
  115. token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
  116. // Test updating a file in repo1 which user2 owns, try both with branch and empty branch
  117. for _, branch := range [...]string{
  118. "master", // Branch
  119. "", // Empty branch
  120. } {
  121. fileID++
  122. treePath := fmt.Sprintf("update/file%d.txt", fileID)
  123. createFile(user2, repo1, treePath)
  124. updateFileOptions := getUpdateFileOptions()
  125. updateFileOptions.BranchName = branch
  126. url := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2)
  127. req := NewRequestWithJSON(t, "PUT", url, &updateFileOptions)
  128. resp := MakeRequest(t, req, http.StatusOK)
  129. gitRepo, _ := git.OpenRepository(stdCtx.Background(), repo1.RepoPath())
  130. commitID, _ := gitRepo.GetBranchCommitID(updateFileOptions.NewBranchName)
  131. lasCommit, _ := gitRepo.GetCommitByPath(treePath)
  132. expectedFileResponse := getExpectedFileResponseForUpdate(commitID, treePath, lasCommit.ID.String())
  133. var fileResponse api.FileResponse
  134. DecodeJSON(t, resp, &fileResponse)
  135. assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content)
  136. assert.EqualValues(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA)
  137. assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL)
  138. assert.EqualValues(t, expectedFileResponse.Commit.Author.Email, fileResponse.Commit.Author.Email)
  139. assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name)
  140. gitRepo.Close()
  141. }
  142. // Test updating a file in a new branch
  143. updateFileOptions := getUpdateFileOptions()
  144. updateFileOptions.BranchName = repo1.DefaultBranch
  145. updateFileOptions.NewBranchName = "new_branch"
  146. fileID++
  147. treePath := fmt.Sprintf("update/file%d.txt", fileID)
  148. createFile(user2, repo1, treePath)
  149. url := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2)
  150. req := NewRequestWithJSON(t, "PUT", url, &updateFileOptions)
  151. resp := MakeRequest(t, req, http.StatusOK)
  152. var fileResponse api.FileResponse
  153. DecodeJSON(t, resp, &fileResponse)
  154. expectedSHA := "08bd14b2e2852529157324de9c226b3364e76136"
  155. expectedHTMLURL := fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/new_branch/update/file%d.txt", fileID)
  156. expectedDownloadURL := fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/new_branch/update/file%d.txt", fileID)
  157. assert.EqualValues(t, expectedSHA, fileResponse.Content.SHA)
  158. assert.EqualValues(t, expectedHTMLURL, *fileResponse.Content.HTMLURL)
  159. assert.EqualValues(t, expectedDownloadURL, *fileResponse.Content.DownloadURL)
  160. assert.EqualValues(t, updateFileOptions.Message+"\n", fileResponse.Commit.Message)
  161. // Test updating a file and renaming it
  162. updateFileOptions = getUpdateFileOptions()
  163. updateFileOptions.BranchName = repo1.DefaultBranch
  164. fileID++
  165. treePath = fmt.Sprintf("update/file%d.txt", fileID)
  166. createFile(user2, repo1, treePath)
  167. updateFileOptions.FromPath = treePath
  168. treePath = "rename/" + treePath
  169. url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2)
  170. req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions)
  171. resp = MakeRequest(t, req, http.StatusOK)
  172. DecodeJSON(t, resp, &fileResponse)
  173. expectedSHA = "08bd14b2e2852529157324de9c226b3364e76136"
  174. expectedHTMLURL = fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/master/rename/update/file%d.txt", fileID)
  175. expectedDownloadURL = fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/master/rename/update/file%d.txt", fileID)
  176. assert.EqualValues(t, expectedSHA, fileResponse.Content.SHA)
  177. assert.EqualValues(t, expectedHTMLURL, *fileResponse.Content.HTMLURL)
  178. assert.EqualValues(t, expectedDownloadURL, *fileResponse.Content.DownloadURL)
  179. // Test updating a file without a message
  180. updateFileOptions = getUpdateFileOptions()
  181. updateFileOptions.Message = ""
  182. updateFileOptions.BranchName = repo1.DefaultBranch
  183. fileID++
  184. treePath = fmt.Sprintf("update/file%d.txt", fileID)
  185. createFile(user2, repo1, treePath)
  186. url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2)
  187. req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions)
  188. resp = MakeRequest(t, req, http.StatusOK)
  189. DecodeJSON(t, resp, &fileResponse)
  190. expectedMessage := "Update " + treePath + "\n"
  191. assert.EqualValues(t, expectedMessage, fileResponse.Commit.Message)
  192. // Test updating a file with the wrong SHA
  193. fileID++
  194. treePath = fmt.Sprintf("update/file%d.txt", fileID)
  195. createFile(user2, repo1, treePath)
  196. updateFileOptions = getUpdateFileOptions()
  197. correctSHA := updateFileOptions.SHA
  198. updateFileOptions.SHA = "badsha"
  199. url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2)
  200. req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions)
  201. resp = MakeRequest(t, req, http.StatusUnprocessableEntity)
  202. expectedAPIError := context.APIError{
  203. Message: "sha does not match [given: " + updateFileOptions.SHA + ", expected: " + correctSHA + "]",
  204. URL: setting.API.SwaggerURL,
  205. }
  206. var apiError context.APIError
  207. DecodeJSON(t, resp, &apiError)
  208. assert.Equal(t, expectedAPIError, apiError)
  209. // Test creating a file in repo1 by user4 who does not have write access
  210. fileID++
  211. treePath = fmt.Sprintf("update/file%d.txt", fileID)
  212. createFile(user2, repo16, treePath)
  213. updateFileOptions = getUpdateFileOptions()
  214. url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token4)
  215. req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions)
  216. MakeRequest(t, req, http.StatusNotFound)
  217. // Tests a repo with no token given so will fail
  218. fileID++
  219. treePath = fmt.Sprintf("update/file%d.txt", fileID)
  220. createFile(user2, repo16, treePath)
  221. updateFileOptions = getUpdateFileOptions()
  222. url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath)
  223. req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions)
  224. MakeRequest(t, req, http.StatusNotFound)
  225. // Test using access token for a private repo that the user of the token owns
  226. fileID++
  227. treePath = fmt.Sprintf("update/file%d.txt", fileID)
  228. createFile(user2, repo16, treePath)
  229. updateFileOptions = getUpdateFileOptions()
  230. url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token2)
  231. req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions)
  232. MakeRequest(t, req, http.StatusOK)
  233. // Test using org repo "org3/repo3" where user2 is a collaborator
  234. fileID++
  235. treePath = fmt.Sprintf("update/file%d.txt", fileID)
  236. createFile(org3, repo3, treePath)
  237. updateFileOptions = getUpdateFileOptions()
  238. url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", org3.Name, repo3.Name, treePath, token2)
  239. req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions)
  240. MakeRequest(t, req, http.StatusOK)
  241. // Test using org repo "org3/repo3" with no user token
  242. fileID++
  243. treePath = fmt.Sprintf("update/file%d.txt", fileID)
  244. createFile(org3, repo3, treePath)
  245. updateFileOptions = getUpdateFileOptions()
  246. url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", org3.Name, repo3.Name, treePath)
  247. req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions)
  248. MakeRequest(t, req, http.StatusNotFound)
  249. // Test using repo "user2/repo1" where user4 is a NOT collaborator
  250. fileID++
  251. treePath = fmt.Sprintf("update/file%d.txt", fileID)
  252. createFile(user2, repo1, treePath)
  253. updateFileOptions = getUpdateFileOptions()
  254. url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token4)
  255. req = NewRequestWithJSON(t, "PUT", url, &updateFileOptions)
  256. MakeRequest(t, req, http.StatusForbidden)
  257. })
  258. }