gitea源码

api_repo_file_update_test.go 12KB

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