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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  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. ContentBase64: 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. onGiteaRun(b, func(b *testing.B, u *url.URL) {
  107. user2 := unittest.AssertExistsAndLoadBean(b, &user_model.User{ID: 2}) // owner of the repo1 & repo16
  108. repo1 := unittest.AssertExistsAndLoadBean(b, &repo_model.Repository{ID: 1}) // public repo
  109. b.ResetTimer()
  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. onGiteaRun(b, func(b *testing.B, u *url.URL) {
  119. user2 := unittest.AssertExistsAndLoadBean(b, &user_model.User{ID: 2}) // owner of the repo1 & repo16
  120. repo1 := unittest.AssertExistsAndLoadBean(b, &repo_model.Repository{ID: 1}) // public repo
  121. b.ResetTimer()
  122. for n := 0; n < b.N; n++ {
  123. treePath := fmt.Sprintf("update/file%d.txt", n)
  124. copy(data, treePath)
  125. _, _ = createFileInBranch(user2, repo1, treePath, repo1.DefaultBranch, treePath)
  126. }
  127. })
  128. }
  129. func TestAPICreateFile(t *testing.T) {
  130. onGiteaRun(t, func(t *testing.T, u *url.URL) {
  131. user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // owner of the repo1 & repo16
  132. org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) // owner of the repo3, is an org
  133. user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) // owner of neither repos
  134. repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) // public repo
  135. repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) // public repo
  136. repo16 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 16}) // private repo
  137. fileID := 0
  138. // Get user2's token
  139. session := loginUser(t, user2.Name)
  140. token2 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
  141. // Get user4's token
  142. session = loginUser(t, user4.Name)
  143. token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
  144. // Test creating a file in repo1 which user2 owns, try both with branch and empty branch
  145. for _, branch := range [...]string{
  146. "master", // Branch
  147. "", // Empty branch
  148. } {
  149. createFileOptions := getCreateFileOptions()
  150. createFileOptions.BranchName = branch
  151. fileID++
  152. treePath := fmt.Sprintf("new/file%d.txt", fileID)
  153. url := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2)
  154. req := NewRequestWithJSON(t, "POST", url, &createFileOptions)
  155. resp := MakeRequest(t, req, http.StatusCreated)
  156. gitRepo, _ := git.OpenRepository(stdCtx.Background(), repo1.RepoPath())
  157. commitID, _ := gitRepo.GetBranchCommitID(createFileOptions.NewBranchName)
  158. latestCommit, _ := gitRepo.GetCommitByPath(treePath)
  159. expectedFileResponse := getExpectedFileResponseForCreate("user2/repo1", commitID, treePath, latestCommit.ID.String())
  160. var fileResponse api.FileResponse
  161. DecodeJSON(t, resp, &fileResponse)
  162. assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content)
  163. assert.EqualValues(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA)
  164. assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL)
  165. assert.EqualValues(t, expectedFileResponse.Commit.Author.Email, fileResponse.Commit.Author.Email)
  166. assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name)
  167. assert.EqualValues(t, expectedFileResponse.Commit.Author.Date, fileResponse.Commit.Author.Date)
  168. assert.EqualValues(t, expectedFileResponse.Commit.Committer.Email, fileResponse.Commit.Committer.Email)
  169. assert.EqualValues(t, expectedFileResponse.Commit.Committer.Name, fileResponse.Commit.Committer.Name)
  170. assert.EqualValues(t, expectedFileResponse.Commit.Committer.Date, fileResponse.Commit.Committer.Date)
  171. gitRepo.Close()
  172. }
  173. // Test creating a file in a new branch
  174. createFileOptions := getCreateFileOptions()
  175. createFileOptions.BranchName = repo1.DefaultBranch
  176. createFileOptions.NewBranchName = "new_branch"
  177. fileID++
  178. treePath := fmt.Sprintf("new/file%d.txt", fileID)
  179. url := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2)
  180. req := NewRequestWithJSON(t, "POST", url, &createFileOptions)
  181. resp := MakeRequest(t, req, http.StatusCreated)
  182. var fileResponse api.FileResponse
  183. DecodeJSON(t, resp, &fileResponse)
  184. expectedSHA := "a635aa942442ddfdba07468cf9661c08fbdf0ebf"
  185. expectedHTMLURL := fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/new_branch/new/file%d.txt", fileID)
  186. expectedDownloadURL := fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/new_branch/new/file%d.txt", fileID)
  187. assert.EqualValues(t, expectedSHA, fileResponse.Content.SHA)
  188. assert.EqualValues(t, expectedHTMLURL, *fileResponse.Content.HTMLURL)
  189. assert.EqualValues(t, expectedDownloadURL, *fileResponse.Content.DownloadURL)
  190. assert.EqualValues(t, createFileOptions.Message+"\n", fileResponse.Commit.Message)
  191. // Test creating a file without a message
  192. createFileOptions = getCreateFileOptions()
  193. createFileOptions.Message = ""
  194. fileID++
  195. treePath = fmt.Sprintf("new/file%d.txt", fileID)
  196. url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2)
  197. req = NewRequestWithJSON(t, "POST", url, &createFileOptions)
  198. resp = MakeRequest(t, req, http.StatusCreated)
  199. DecodeJSON(t, resp, &fileResponse)
  200. expectedMessage := "Add " + treePath + "\n"
  201. assert.EqualValues(t, expectedMessage, fileResponse.Commit.Message)
  202. // Test trying to create a file that already exists, should fail
  203. createFileOptions = getCreateFileOptions()
  204. treePath = "README.md"
  205. url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token2)
  206. req = NewRequestWithJSON(t, "POST", url, &createFileOptions)
  207. resp = MakeRequest(t, req, http.StatusUnprocessableEntity)
  208. expectedAPIError := context.APIError{
  209. Message: "repository file already exists [path: " + treePath + "]",
  210. URL: setting.API.SwaggerURL,
  211. }
  212. var apiError context.APIError
  213. DecodeJSON(t, resp, &apiError)
  214. assert.Equal(t, expectedAPIError, apiError)
  215. // Test creating a file in repo1 by user4 who does not have write access
  216. createFileOptions = getCreateFileOptions()
  217. fileID++
  218. treePath = fmt.Sprintf("new/file%d.txt", fileID)
  219. url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token4)
  220. req = NewRequestWithJSON(t, "POST", url, &createFileOptions)
  221. MakeRequest(t, req, http.StatusNotFound)
  222. // Tests a repo with no token given so will fail
  223. createFileOptions = getCreateFileOptions()
  224. fileID++
  225. treePath = fmt.Sprintf("new/file%d.txt", fileID)
  226. url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath)
  227. req = NewRequestWithJSON(t, "POST", url, &createFileOptions)
  228. MakeRequest(t, req, http.StatusNotFound)
  229. // Test using access token for a private repo that the user of the token owns
  230. createFileOptions = getCreateFileOptions()
  231. fileID++
  232. treePath = fmt.Sprintf("new/file%d.txt", fileID)
  233. url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo16.Name, treePath, token2)
  234. req = NewRequestWithJSON(t, "POST", url, &createFileOptions)
  235. MakeRequest(t, req, http.StatusCreated)
  236. // Test using org repo "org3/repo3" where user2 is a collaborator
  237. createFileOptions = getCreateFileOptions()
  238. fileID++
  239. treePath = fmt.Sprintf("new/file%d.txt", fileID)
  240. url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", org3.Name, repo3.Name, treePath, token2)
  241. req = NewRequestWithJSON(t, "POST", url, &createFileOptions)
  242. MakeRequest(t, req, http.StatusCreated)
  243. // Test using org repo "org3/repo3" with no user token
  244. createFileOptions = getCreateFileOptions()
  245. fileID++
  246. treePath = fmt.Sprintf("new/file%d.txt", fileID)
  247. url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", org3.Name, repo3.Name, treePath)
  248. req = NewRequestWithJSON(t, "POST", url, &createFileOptions)
  249. MakeRequest(t, req, http.StatusNotFound)
  250. // Test using repo "user2/repo1" where user4 is a NOT collaborator
  251. createFileOptions = getCreateFileOptions()
  252. fileID++
  253. treePath = fmt.Sprintf("new/file%d.txt", fileID)
  254. url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, repo1.Name, treePath, token4)
  255. req = NewRequestWithJSON(t, "POST", url, &createFileOptions)
  256. MakeRequest(t, req, http.StatusForbidden)
  257. // Test creating a file in an empty repository
  258. doAPICreateRepository(NewAPITestContext(t, "user2", "empty-repo", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser), true)(t)
  259. createFileOptions = getCreateFileOptions()
  260. fileID++
  261. treePath = fmt.Sprintf("new/file%d.txt", fileID)
  262. url = fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s?token=%s", user2.Name, "empty-repo", treePath, token2)
  263. req = NewRequestWithJSON(t, "POST", url, &createFileOptions)
  264. resp = MakeRequest(t, req, http.StatusCreated)
  265. emptyRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "empty-repo"}) // public repo
  266. gitRepo, _ := git.OpenRepository(stdCtx.Background(), emptyRepo.RepoPath())
  267. commitID, _ := gitRepo.GetBranchCommitID(createFileOptions.NewBranchName)
  268. latestCommit, _ := gitRepo.GetCommitByPath(treePath)
  269. expectedFileResponse := getExpectedFileResponseForCreate("user2/empty-repo", commitID, treePath, latestCommit.ID.String())
  270. DecodeJSON(t, resp, &fileResponse)
  271. assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content)
  272. assert.EqualValues(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA)
  273. assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL)
  274. assert.EqualValues(t, expectedFileResponse.Commit.Author.Email, fileResponse.Commit.Author.Email)
  275. assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name)
  276. assert.EqualValues(t, expectedFileResponse.Commit.Author.Date, fileResponse.Commit.Author.Date)
  277. assert.EqualValues(t, expectedFileResponse.Commit.Committer.Email, fileResponse.Commit.Committer.Email)
  278. assert.EqualValues(t, expectedFileResponse.Commit.Committer.Name, fileResponse.Commit.Committer.Name)
  279. assert.EqualValues(t, expectedFileResponse.Commit.Committer.Date, fileResponse.Commit.Committer.Date)
  280. gitRepo.Close()
  281. })
  282. }