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