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

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