gitea源码

api_repo_file_create_test.go 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  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. "time"
  12. auth_model "code.gitea.io/gitea/models/auth"
  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/gitrepo"
  17. "code.gitea.io/gitea/modules/setting"
  18. api "code.gitea.io/gitea/modules/structs"
  19. "code.gitea.io/gitea/modules/util"
  20. "code.gitea.io/gitea/services/context"
  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 normalizeFileContentResponseCommitTime(c *api.ContentsResponse) {
  48. // decoded JSON response may contain different timezone from the one parsed by git commit
  49. // so we need to normalize the time to UTC to make "assert.Equal" pass
  50. c.LastCommitterDate = util.ToPointer(c.LastCommitterDate.UTC())
  51. c.LastAuthorDate = util.ToPointer(c.LastAuthorDate.UTC())
  52. }
  53. type apiFileResponseInfo struct {
  54. repoFullName, commitID, treePath, lastCommitSHA string
  55. lastCommitterWhen, lastAuthorWhen time.Time
  56. }
  57. func getExpectedFileResponseForCreate(info apiFileResponseInfo) *api.FileResponse {
  58. sha := "a635aa942442ddfdba07468cf9661c08fbdf0ebf"
  59. encoding := "base64"
  60. content := "VGhpcyBpcyBuZXcgdGV4dA=="
  61. selfURL := setting.AppURL + "api/v1/repos/" + info.repoFullName + "/contents/" + info.treePath + "?ref=master"
  62. htmlURL := setting.AppURL + info.repoFullName + "/src/branch/master/" + info.treePath
  63. gitURL := setting.AppURL + "api/v1/repos/" + info.repoFullName + "/git/blobs/" + sha
  64. downloadURL := setting.AppURL + info.repoFullName + "/raw/branch/master/" + info.treePath
  65. ret := &api.FileResponse{
  66. Content: &api.ContentsResponse{
  67. Name: path.Base(info.treePath),
  68. Path: info.treePath,
  69. SHA: sha,
  70. LastCommitSHA: util.ToPointer(info.lastCommitSHA),
  71. LastCommitterDate: util.ToPointer(info.lastCommitterWhen),
  72. LastAuthorDate: util.ToPointer(info.lastAuthorWhen),
  73. Size: 16,
  74. Type: "file",
  75. Encoding: &encoding,
  76. Content: &content,
  77. URL: &selfURL,
  78. HTMLURL: &htmlURL,
  79. GitURL: &gitURL,
  80. DownloadURL: &downloadURL,
  81. Links: &api.FileLinksResponse{
  82. Self: &selfURL,
  83. GitURL: &gitURL,
  84. HTMLURL: &htmlURL,
  85. },
  86. },
  87. Commit: &api.FileCommitResponse{
  88. CommitMeta: api.CommitMeta{
  89. URL: setting.AppURL + "api/v1/repos/" + info.repoFullName + "/git/commits/" + info.commitID,
  90. SHA: info.commitID,
  91. },
  92. HTMLURL: setting.AppURL + info.repoFullName + "/commit/" + info.commitID,
  93. Author: &api.CommitUser{
  94. Identity: api.Identity{
  95. Name: "Anne Doe",
  96. Email: "annedoe@example.com",
  97. },
  98. Date: "2000-01-01T00:00:10Z",
  99. },
  100. Committer: &api.CommitUser{
  101. Identity: api.Identity{
  102. Name: "John Doe",
  103. Email: "johndoe@example.com",
  104. },
  105. Date: "2000-12-31T23:59:50Z",
  106. },
  107. Message: "Updates README.md\n",
  108. },
  109. Verification: &api.PayloadCommitVerification{
  110. Verified: false,
  111. Reason: "gpg.error.not_signed_commit",
  112. Signature: "",
  113. Payload: "",
  114. },
  115. }
  116. normalizeFileContentResponseCommitTime(ret.Content)
  117. return ret
  118. }
  119. func BenchmarkAPICreateFileSmall(b *testing.B) {
  120. onGiteaRun(b, func(b *testing.B, u *url.URL) {
  121. user2 := unittest.AssertExistsAndLoadBean(b, &user_model.User{ID: 2}) // owner of the repo1 & repo16
  122. repo1 := unittest.AssertExistsAndLoadBean(b, &repo_model.Repository{ID: 1}) // public repo
  123. b.ResetTimer()
  124. for n := 0; b.Loop(); n++ {
  125. treePath := fmt.Sprintf("update/file%d.txt", n)
  126. _, _ = createFileInBranch(user2, repo1, treePath, repo1.DefaultBranch, treePath)
  127. }
  128. })
  129. }
  130. func BenchmarkAPICreateFileMedium(b *testing.B) {
  131. data := make([]byte, 10*1024*1024)
  132. onGiteaRun(b, func(b *testing.B, u *url.URL) {
  133. user2 := unittest.AssertExistsAndLoadBean(b, &user_model.User{ID: 2}) // owner of the repo1 & repo16
  134. repo1 := unittest.AssertExistsAndLoadBean(b, &repo_model.Repository{ID: 1}) // public repo
  135. b.ResetTimer()
  136. for n := 0; b.Loop(); n++ {
  137. treePath := fmt.Sprintf("update/file%d.txt", n)
  138. copy(data, treePath)
  139. _, _ = createFileInBranch(user2, repo1, treePath, repo1.DefaultBranch, treePath)
  140. }
  141. })
  142. }
  143. func TestAPICreateFile(t *testing.T) {
  144. onGiteaRun(t, func(t *testing.T, u *url.URL) {
  145. user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // owner of the repo1 & repo16
  146. org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) // owner of the repo3, is an org
  147. user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) // owner of neither repos
  148. repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) // public repo
  149. repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) // public repo
  150. repo16 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 16}) // private repo
  151. fileID := 0
  152. // Get user2's token
  153. session := loginUser(t, user2.Name)
  154. token2 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
  155. // Get user4's token
  156. session = loginUser(t, user4.Name)
  157. token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
  158. // Test creating a file in repo1 which user2 owns, try both with branch and empty branch
  159. for _, branch := range [...]string{
  160. "master", // Branch
  161. "", // Empty branch
  162. } {
  163. createFileOptions := getCreateFileOptions()
  164. createFileOptions.BranchName = branch
  165. fileID++
  166. treePath := fmt.Sprintf("new/file%d.txt", fileID)
  167. req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &createFileOptions).
  168. AddTokenAuth(token2)
  169. resp := MakeRequest(t, req, http.StatusCreated)
  170. gitRepo, _ := gitrepo.OpenRepository(t.Context(), repo1)
  171. defer gitRepo.Close()
  172. commitID, _ := gitRepo.GetBranchCommitID(createFileOptions.NewBranchName)
  173. lastCommit, _ := gitRepo.GetCommitByPath(treePath)
  174. expectedFileResponse := getExpectedFileResponseForCreate(apiFileResponseInfo{
  175. repoFullName: "user2/repo1",
  176. commitID: commitID,
  177. treePath: treePath,
  178. lastCommitSHA: lastCommit.ID.String(),
  179. lastCommitterWhen: lastCommit.Committer.When,
  180. lastAuthorWhen: lastCommit.Author.When,
  181. })
  182. var fileResponse api.FileResponse
  183. DecodeJSON(t, resp, &fileResponse)
  184. normalizeFileContentResponseCommitTime(fileResponse.Content)
  185. assert.Equal(t, expectedFileResponse.Content, fileResponse.Content)
  186. assert.Equal(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA)
  187. assert.Equal(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL)
  188. assert.Equal(t, expectedFileResponse.Commit.Author.Email, fileResponse.Commit.Author.Email)
  189. assert.Equal(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name)
  190. assert.Equal(t, expectedFileResponse.Commit.Author.Date, fileResponse.Commit.Author.Date)
  191. assert.Equal(t, expectedFileResponse.Commit.Committer.Email, fileResponse.Commit.Committer.Email)
  192. assert.Equal(t, expectedFileResponse.Commit.Committer.Name, fileResponse.Commit.Committer.Name)
  193. assert.Equal(t, expectedFileResponse.Commit.Committer.Date, fileResponse.Commit.Committer.Date)
  194. }
  195. // Test creating a file in a new branch
  196. createFileOptions := getCreateFileOptions()
  197. createFileOptions.BranchName = repo1.DefaultBranch
  198. createFileOptions.NewBranchName = "new_branch"
  199. fileID++
  200. treePath := fmt.Sprintf("new/file%d.txt", fileID)
  201. req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &createFileOptions).
  202. AddTokenAuth(token2)
  203. resp := MakeRequest(t, req, http.StatusCreated)
  204. var fileResponse api.FileResponse
  205. DecodeJSON(t, resp, &fileResponse)
  206. expectedSHA := "a635aa942442ddfdba07468cf9661c08fbdf0ebf"
  207. expectedHTMLURL := fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/new_branch/new/file%d.txt", fileID)
  208. expectedDownloadURL := fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/new_branch/new/file%d.txt", fileID)
  209. assert.Equal(t, expectedSHA, fileResponse.Content.SHA)
  210. assert.Equal(t, expectedHTMLURL, *fileResponse.Content.HTMLURL)
  211. assert.Equal(t, expectedDownloadURL, *fileResponse.Content.DownloadURL)
  212. assert.Equal(t, createFileOptions.Message+"\n", fileResponse.Commit.Message)
  213. // Test creating a file without a message
  214. createFileOptions = getCreateFileOptions()
  215. createFileOptions.Message = ""
  216. fileID++
  217. treePath = fmt.Sprintf("new/file%d.txt", fileID)
  218. req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &createFileOptions).
  219. AddTokenAuth(token2)
  220. resp = MakeRequest(t, req, http.StatusCreated)
  221. DecodeJSON(t, resp, &fileResponse)
  222. expectedMessage := "Add " + treePath + "\n"
  223. assert.Equal(t, expectedMessage, fileResponse.Commit.Message)
  224. // Test trying to create a file that already exists, should fail
  225. createFileOptions = getCreateFileOptions()
  226. treePath = "README.md"
  227. req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &createFileOptions).
  228. AddTokenAuth(token2)
  229. resp = MakeRequest(t, req, http.StatusUnprocessableEntity)
  230. expectedAPIError := context.APIError{
  231. Message: "repository file already exists [path: " + treePath + "]",
  232. URL: setting.API.SwaggerURL,
  233. }
  234. var apiError context.APIError
  235. DecodeJSON(t, resp, &apiError)
  236. assert.Equal(t, expectedAPIError, apiError)
  237. // Test creating a file in repo1 by user4 who does not have write access
  238. createFileOptions = getCreateFileOptions()
  239. fileID++
  240. treePath = fmt.Sprintf("new/file%d.txt", fileID)
  241. req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath), &createFileOptions).
  242. AddTokenAuth(token4)
  243. MakeRequest(t, req, http.StatusNotFound)
  244. // Tests a repo with no token given so will fail
  245. createFileOptions = getCreateFileOptions()
  246. fileID++
  247. treePath = fmt.Sprintf("new/file%d.txt", fileID)
  248. req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath), &createFileOptions)
  249. MakeRequest(t, req, http.StatusNotFound)
  250. // Test using access token for a private repo that the user of the token owns
  251. createFileOptions = getCreateFileOptions()
  252. fileID++
  253. treePath = fmt.Sprintf("new/file%d.txt", fileID)
  254. req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo16.Name, treePath), &createFileOptions).
  255. AddTokenAuth(token2)
  256. MakeRequest(t, req, http.StatusCreated)
  257. // Test using org repo "org3/repo3" where user2 is a collaborator
  258. createFileOptions = getCreateFileOptions()
  259. fileID++
  260. treePath = fmt.Sprintf("new/file%d.txt", fileID)
  261. req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", org3.Name, repo3.Name, treePath), &createFileOptions).
  262. AddTokenAuth(token2)
  263. MakeRequest(t, req, http.StatusCreated)
  264. // Test using org repo "org3/repo3" with no user token
  265. createFileOptions = getCreateFileOptions()
  266. fileID++
  267. treePath = fmt.Sprintf("new/file%d.txt", fileID)
  268. req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", org3.Name, repo3.Name, treePath), &createFileOptions)
  269. MakeRequest(t, req, http.StatusNotFound)
  270. // Test using repo "user2/repo1" where user4 is a NOT collaborator
  271. createFileOptions = getCreateFileOptions()
  272. fileID++
  273. treePath = fmt.Sprintf("new/file%d.txt", fileID)
  274. req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, repo1.Name, treePath), &createFileOptions).
  275. AddTokenAuth(token4)
  276. MakeRequest(t, req, http.StatusForbidden)
  277. // Test creating a file in an empty repository
  278. doAPICreateRepository(NewAPITestContext(t, "user2", "empty-repo", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser), true)(t)
  279. createFileOptions = getCreateFileOptions()
  280. fileID++
  281. treePath = fmt.Sprintf("new/file%d.txt", fileID)
  282. req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", user2.Name, "empty-repo", treePath), &createFileOptions).
  283. AddTokenAuth(token2)
  284. resp = MakeRequest(t, req, http.StatusCreated)
  285. emptyRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "empty-repo"}) // public repo
  286. gitRepo, _ := gitrepo.OpenRepository(t.Context(), emptyRepo)
  287. defer gitRepo.Close()
  288. commitID, _ := gitRepo.GetBranchCommitID(createFileOptions.NewBranchName)
  289. latestCommit, _ := gitRepo.GetCommitByPath(treePath)
  290. expectedFileResponse := getExpectedFileResponseForCreate(apiFileResponseInfo{
  291. repoFullName: "user2/empty-repo",
  292. commitID: commitID,
  293. treePath: treePath,
  294. lastCommitSHA: latestCommit.ID.String(),
  295. lastCommitterWhen: latestCommit.Committer.When,
  296. lastAuthorWhen: latestCommit.Author.When,
  297. })
  298. DecodeJSON(t, resp, &fileResponse)
  299. normalizeFileContentResponseCommitTime(fileResponse.Content)
  300. assert.Equal(t, expectedFileResponse.Content, fileResponse.Content)
  301. assert.Equal(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA)
  302. assert.Equal(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL)
  303. assert.Equal(t, expectedFileResponse.Commit.Author.Email, fileResponse.Commit.Author.Email)
  304. assert.Equal(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name)
  305. assert.Equal(t, expectedFileResponse.Commit.Author.Date, fileResponse.Commit.Author.Date)
  306. assert.Equal(t, expectedFileResponse.Commit.Committer.Email, fileResponse.Commit.Committer.Email)
  307. assert.Equal(t, expectedFileResponse.Commit.Committer.Name, fileResponse.Commit.Committer.Name)
  308. assert.Equal(t, expectedFileResponse.Commit.Committer.Date, fileResponse.Commit.Committer.Date)
  309. })
  310. }