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