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_create_test.go 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  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. "time"
  13. auth_model "code.gitea.io/gitea/models/auth"
  14. repo_model "code.gitea.io/gitea/models/repo"
  15. "code.gitea.io/gitea/models/unittest"
  16. user_model "code.gitea.io/gitea/models/user"
  17. "code.gitea.io/gitea/modules/context"
  18. "code.gitea.io/gitea/modules/git"
  19. "code.gitea.io/gitea/modules/setting"
  20. api "code.gitea.io/gitea/modules/structs"
  21. "github.com/stretchr/testify/assert"
  22. )
  23. func getCreateFileOptions() api.CreateFileOptions {
  24. content := "This is new text"
  25. contentEncoded := base64.StdEncoding.EncodeToString([]byte(content))
  26. return api.CreateFileOptions{
  27. FileOptions: api.FileOptions{
  28. BranchName: "master",
  29. NewBranchName: "master",
  30. Message: "Making this new file 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. Dates: api.CommitDateOptions{
  40. Author: time.Unix(946684810, 0),
  41. Committer: time.Unix(978307190, 0),
  42. },
  43. },
  44. Content: contentEncoded,
  45. }
  46. }
  47. func getExpectedFileResponseForCreate(repoFullName, commitID, treePath, latestCommitSHA string) *api.FileResponse {
  48. sha := "a635aa942442ddfdba07468cf9661c08fbdf0ebf"
  49. encoding := "base64"
  50. content := "VGhpcyBpcyBuZXcgdGV4dA=="
  51. selfURL := setting.AppURL + "api/v1/repos/" + repoFullName + "/contents/" + treePath + "?ref=master"
  52. htmlURL := setting.AppURL + repoFullName + "/src/branch/master/" + treePath
  53. gitURL := setting.AppURL + "api/v1/repos/" + repoFullName + "/git/blobs/" + sha
  54. downloadURL := setting.AppURL + repoFullName + "/raw/branch/master/" + treePath
  55. return &api.FileResponse{
  56. Content: &api.ContentsResponse{
  57. Name: filepath.Base(treePath),
  58. Path: treePath,
  59. SHA: sha,
  60. LastCommitSHA: latestCommitSHA,
  61. Size: 16,
  62. Type: "file",
  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/" + repoFullName + "/git/commits/" + commitID,
  78. SHA: commitID,
  79. },
  80. HTMLURL: setting.AppURL + repoFullName + "/commit/" + commitID,
  81. Author: &api.CommitUser{
  82. Identity: api.Identity{
  83. Name: "Anne Doe",
  84. Email: "annedoe@example.com",
  85. },
  86. Date: "2000-01-01T00:00:10Z",
  87. },
  88. Committer: &api.CommitUser{
  89. Identity: api.Identity{
  90. Name: "John Doe",
  91. Email: "johndoe@example.com",
  92. },
  93. Date: "2000-12-31T23:59:50Z",
  94. },
  95. Message: "Updates README.md\n",
  96. },
  97. Verification: &api.PayloadCommitVerification{
  98. Verified: false,
  99. Reason: "gpg.error.not_signed_commit",
  100. Signature: "",
  101. Payload: "",
  102. },
  103. }
  104. }
  105. func BenchmarkAPICreateFileSmall(b *testing.B) {
  106. onGiteaRunTB(b, func(t testing.TB, u *url.URL) {
  107. b := t.(*testing.B)
  108. user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // owner of the repo1 & repo16
  109. repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) // public repo
  110. for n := 0; n < b.N; n++ {
  111. treePath := fmt.Sprintf("update/file%d.txt", n)
  112. createFileInBranch(user2, repo1, treePath, repo1.DefaultBranch, treePath)
  113. }
  114. })
  115. }
  116. func BenchmarkAPICreateFileMedium(b *testing.B) {
  117. data := make([]byte, 10*1024*1024)
  118. onGiteaRunTB(b, func(t testing.TB, u *url.URL) {
  119. b := t.(*testing.B)
  120. user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // owner of the repo1 & repo16
  121. repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) // public repo
  122. b.ResetTimer()
  123. for n := 0; n < b.N; n++ {
  124. treePath := fmt.Sprintf("update/file%d.txt", n)
  125. copy(data, treePath)
  126. createFileInBranch(user2, repo1, treePath, repo1.DefaultBranch, treePath)
  127. }
  128. })
  129. }
  130. func TestAPICreateFile(t *testing.T) {
  131. onGiteaRun(t, func(t *testing.T, u *url.URL) {
  132. user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // owner of the repo1 & repo16
  133. user3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) // owner of the repo3, is an org
  134. user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) // owner of neither repos
  135. repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) // public repo
  136. repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) // public repo
  137. repo16 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 16}) // private repo
  138. fileID := 0
  139. // Get user2's token
  140. session := loginUser(t, user2.Name)
  141. token2 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo)
  142. // Get user4's token
  143. session = loginUser(t, user4.Name)
  144. token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeRepo)
  145. // Test creating a file in repo1 which user2 owns, try both with branch and empty branch
  146. for _, branch := range [...]string{
  147. "master", // Branch
  148. "", // Empty branch
  149. } {
  150. createFileOptions := getCreateFileOptions()
  151. createFileOptions.BranchName = branch
  152. fileID++
  153. treePath := fmt.Sprintf("new/file%d.txt", fileID)
  154. url := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2)
  155. req := NewRequestWithJSON(t, "POST", url, &createFileOptions)
  156. resp := MakeRequest(t, req, http.StatusCreated)
  157. gitRepo, _ := git.OpenRepository(stdCtx.Background(), repo1.RepoPath())
  158. commitID, _ := gitRepo.GetBranchCommitID(createFileOptions.NewBranchName)
  159. latestCommit, _ := gitRepo.GetCommitByPath(treePath)
  160. expectedFileResponse := getExpectedFileResponseForCreate("user2/repo1", commitID, treePath, latestCommit.ID.String())
  161. var fileResponse api.FileResponse
  162. DecodeJSON(t, resp, &fileResponse)
  163. assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content)
  164. assert.EqualValues(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA)
  165. assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL)
  166. assert.EqualValues(t, expectedFileResponse.Commit.Author.Email, fileResponse.Commit.Author.Email)
  167. assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name)
  168. assert.EqualValues(t, expectedFileResponse.Commit.Author.Date, fileResponse.Commit.Author.Date)
  169. assert.EqualValues(t, expectedFileResponse.Commit.Committer.Email, fileResponse.Commit.Committer.Email)
  170. assert.EqualValues(t, expectedFileResponse.Commit.Committer.Name, fileResponse.Commit.Committer.Name)
  171. assert.EqualValues(t, expectedFileResponse.Commit.Committer.Date, fileResponse.Commit.Committer.Date)
  172. gitRepo.Close()
  173. }
  174. // Test creating a file in a new branch
  175. createFileOptions := getCreateFileOptions()
  176. createFileOptions.BranchName = repo1.DefaultBranch
  177. createFileOptions.NewBranchName = "new_branch"
  178. fileID++
  179. treePath := fmt.Sprintf("new/file%d.txt", fileID)
  180. url := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2)
  181. req := NewRequestWithJSON(t, "POST", url, &createFileOptions)
  182. resp := MakeRequest(t, req, http.StatusCreated)
  183. var fileResponse api.FileResponse
  184. DecodeJSON(t, resp, &fileResponse)
  185. expectedSHA := "a635aa942442ddfdba07468cf9661c08fbdf0ebf"
  186. expectedHTMLURL := fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/new_branch/new/file%d.txt", fileID)
  187. expectedDownloadURL := fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/new_branch/new/file%d.txt", fileID)
  188. assert.EqualValues(t, expectedSHA, fileResponse.Content.SHA)
  189. assert.EqualValues(t, expectedHTMLURL, *fileResponse.Content.HTMLURL)
  190. assert.EqualValues(t, expectedDownloadURL, *fileResponse.Content.DownloadURL)
  191. assert.EqualValues(t, createFileOptions.Message+"\n", fileResponse.Commit.Message)
  192. // Test creating a file without a message
  193. createFileOptions = getCreateFileOptions()
  194. createFileOptions.Message = ""
  195. fileID++
  196. treePath = fmt.Sprintf("new/file%d.txt", fileID)
  197. url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2)
  198. req = NewRequestWithJSON(t, "POST", url, &createFileOptions)
  199. resp = MakeRequest(t, req, http.StatusCreated)
  200. DecodeJSON(t, resp, &fileResponse)
  201. expectedMessage := "Add '" + treePath + "'\n"
  202. assert.EqualValues(t, expectedMessage, fileResponse.Commit.Message)
  203. // Test trying to create a file that already exists, should fail
  204. createFileOptions = getCreateFileOptions()
  205. treePath = "README.md"
  206. url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2)
  207. req = NewRequestWithJSON(t, "POST", url, &createFileOptions)
  208. resp = MakeRequest(t, req, http.StatusUnprocessableEntity)
  209. expectedAPIError := context.APIError{
  210. Message: "repository file already exists [path: " + treePath + "]",
  211. URL: setting.API.SwaggerURL,
  212. }
  213. var apiError context.APIError
  214. DecodeJSON(t, resp, &apiError)
  215. assert.Equal(t, expectedAPIError, apiError)
  216. // Test creating a file in repo1 by user4 who does not have write access
  217. createFileOptions = getCreateFileOptions()
  218. fileID++
  219. treePath = fmt.Sprintf("new/file%d.txt", fileID)
  220. url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token4)
  221. req = NewRequestWithJSON(t, "POST", url, &createFileOptions)
  222. MakeRequest(t, req, http.StatusNotFound)
  223. // Tests a repo with no token given so will fail
  224. createFileOptions = getCreateFileOptions()
  225. fileID++
  226. treePath = fmt.Sprintf("new/file%d.txt", fileID)
  227. url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath)
  228. req = NewRequestWithJSON(t, "POST", url, &createFileOptions)
  229. MakeRequest(t, req, http.StatusNotFound)
  230. // Test using access token for a private repo that the user of the token owns
  231. createFileOptions = getCreateFileOptions()
  232. fileID++
  233. treePath = fmt.Sprintf("new/file%d.txt", fileID)
  234. url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token2)
  235. req = NewRequestWithJSON(t, "POST", url, &createFileOptions)
  236. MakeRequest(t, req, http.StatusCreated)
  237. // Test using org repo "user3/repo3" where user2 is a collaborator
  238. createFileOptions = getCreateFileOptions()
  239. fileID++
  240. treePath = fmt.Sprintf("new/file%d.txt", fileID)
  241. url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user3.Name, repo3.Name, treePath, token2)
  242. req = NewRequestWithJSON(t, "POST", url, &createFileOptions)
  243. MakeRequest(t, req, http.StatusCreated)
  244. // Test using org repo "user3/repo3" with no user token
  245. createFileOptions = getCreateFileOptions()
  246. fileID++
  247. treePath = fmt.Sprintf("new/file%d.txt", fileID)
  248. url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user3.Name, repo3.Name, treePath)
  249. req = NewRequestWithJSON(t, "POST", url, &createFileOptions)
  250. MakeRequest(t, req, http.StatusNotFound)
  251. // Test using repo "user2/repo1" where user4 is a NOT collaborator
  252. createFileOptions = getCreateFileOptions()
  253. fileID++
  254. treePath = fmt.Sprintf("new/file%d.txt", fileID)
  255. url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token4)
  256. req = NewRequestWithJSON(t, "POST", url, &createFileOptions)
  257. MakeRequest(t, req, http.StatusForbidden)
  258. // Test creating a file in an empty repository
  259. doAPICreateRepository(NewAPITestContext(t, "user2", "empty-repo", auth_model.AccessTokenScopeRepo), true)(t)
  260. createFileOptions = getCreateFileOptions()
  261. fileID++
  262. treePath = fmt.Sprintf("new/file%d.txt", fileID)
  263. url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, "empty-repo", treePath, token2)
  264. req = NewRequestWithJSON(t, "POST", url, &createFileOptions)
  265. resp = MakeRequest(t, req, http.StatusCreated)
  266. emptyRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "empty-repo"}) // public repo
  267. gitRepo, _ := git.OpenRepository(stdCtx.Background(), emptyRepo.RepoPath())
  268. commitID, _ := gitRepo.GetBranchCommitID(createFileOptions.NewBranchName)
  269. latestCommit, _ := gitRepo.GetCommitByPath(treePath)
  270. expectedFileResponse := getExpectedFileResponseForCreate("user2/empty-repo", commitID, treePath, latestCommit.ID.String())
  271. DecodeJSON(t, resp, &fileResponse)
  272. assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content)
  273. assert.EqualValues(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA)
  274. assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL)
  275. assert.EqualValues(t, expectedFileResponse.Commit.Author.Email, fileResponse.Commit.Author.Email)
  276. assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name)
  277. assert.EqualValues(t, expectedFileResponse.Commit.Author.Date, fileResponse.Commit.Author.Date)
  278. assert.EqualValues(t, expectedFileResponse.Commit.Committer.Email, fileResponse.Commit.Committer.Email)
  279. assert.EqualValues(t, expectedFileResponse.Commit.Committer.Name, fileResponse.Commit.Committer.Name)
  280. assert.EqualValues(t, expectedFileResponse.Commit.Committer.Date, fileResponse.Commit.Committer.Date)
  281. gitRepo.Close()
  282. })
  283. }