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

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