code.gitea.io/gitea@v1.22.3/tests/integration/api_repo_files_change_test.go (about) 1 // Copyright 2023 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 "testing" 13 14 auth_model "code.gitea.io/gitea/models/auth" 15 repo_model "code.gitea.io/gitea/models/repo" 16 "code.gitea.io/gitea/models/unittest" 17 user_model "code.gitea.io/gitea/models/user" 18 "code.gitea.io/gitea/modules/gitrepo" 19 "code.gitea.io/gitea/modules/setting" 20 api "code.gitea.io/gitea/modules/structs" 21 "code.gitea.io/gitea/services/context" 22 23 "github.com/stretchr/testify/assert" 24 ) 25 26 func getChangeFilesOptions() *api.ChangeFilesOptions { 27 newContent := "This is new text" 28 updateContent := "This is updated text" 29 newContentEncoded := base64.StdEncoding.EncodeToString([]byte(newContent)) 30 updateContentEncoded := base64.StdEncoding.EncodeToString([]byte(updateContent)) 31 return &api.ChangeFilesOptions{ 32 FileOptions: api.FileOptions{ 33 BranchName: "master", 34 NewBranchName: "master", 35 Message: "My update of 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 }, 45 Files: []*api.ChangeFileOperation{ 46 { 47 Operation: "create", 48 ContentBase64: newContentEncoded, 49 }, 50 { 51 Operation: "update", 52 ContentBase64: updateContentEncoded, 53 SHA: "103ff9234cefeee5ec5361d22b49fbb04d385885", 54 }, 55 { 56 Operation: "delete", 57 SHA: "103ff9234cefeee5ec5361d22b49fbb04d385885", 58 }, 59 }, 60 } 61 } 62 63 func TestAPIChangeFiles(t *testing.T) { 64 onGiteaRun(t, func(t *testing.T, u *url.URL) { 65 user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // owner of the repo1 & repo16 66 org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) // owner of the repo3, is an org 67 user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) // owner of neither repos 68 repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) // public repo 69 repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) // public repo 70 repo16 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 16}) // private repo 71 fileID := 0 72 73 // Get user2's token 74 session := loginUser(t, user2.Name) 75 token2 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) 76 // Get user4's token 77 session = loginUser(t, user4.Name) 78 token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) 79 80 // Test changing files in repo1 which user2 owns, try both with branch and empty branch 81 for _, branch := range [...]string{ 82 "master", // Branch 83 "", // Empty branch 84 } { 85 fileID++ 86 createTreePath := fmt.Sprintf("new/file%d.txt", fileID) 87 updateTreePath := fmt.Sprintf("update/file%d.txt", fileID) 88 deleteTreePath := fmt.Sprintf("delete/file%d.txt", fileID) 89 createFile(user2, repo1, updateTreePath) 90 createFile(user2, repo1, deleteTreePath) 91 changeFilesOptions := getChangeFilesOptions() 92 changeFilesOptions.BranchName = branch 93 changeFilesOptions.Files[0].Path = createTreePath 94 changeFilesOptions.Files[1].Path = updateTreePath 95 changeFilesOptions.Files[2].Path = deleteTreePath 96 req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents", user2.Name, repo1.Name), &changeFilesOptions). 97 AddTokenAuth(token2) 98 resp := MakeRequest(t, req, http.StatusCreated) 99 gitRepo, _ := gitrepo.OpenRepository(stdCtx.Background(), repo1) 100 commitID, _ := gitRepo.GetBranchCommitID(changeFilesOptions.NewBranchName) 101 createLasCommit, _ := gitRepo.GetCommitByPath(createTreePath) 102 updateLastCommit, _ := gitRepo.GetCommitByPath(updateTreePath) 103 expectedCreateFileResponse := getExpectedFileResponseForCreate(fmt.Sprintf("%v/%v", user2.Name, repo1.Name), commitID, createTreePath, createLasCommit.ID.String()) 104 expectedUpdateFileResponse := getExpectedFileResponseForUpdate(commitID, updateTreePath, updateLastCommit.ID.String()) 105 var filesResponse api.FilesResponse 106 DecodeJSON(t, resp, &filesResponse) 107 108 // check create file 109 assert.EqualValues(t, expectedCreateFileResponse.Content, filesResponse.Files[0]) 110 111 // check update file 112 assert.EqualValues(t, expectedUpdateFileResponse.Content, filesResponse.Files[1]) 113 114 // test commit info 115 assert.EqualValues(t, expectedCreateFileResponse.Commit.SHA, filesResponse.Commit.SHA) 116 assert.EqualValues(t, expectedCreateFileResponse.Commit.HTMLURL, filesResponse.Commit.HTMLURL) 117 assert.EqualValues(t, expectedCreateFileResponse.Commit.Author.Email, filesResponse.Commit.Author.Email) 118 assert.EqualValues(t, expectedCreateFileResponse.Commit.Author.Name, filesResponse.Commit.Author.Name) 119 assert.EqualValues(t, expectedCreateFileResponse.Commit.Committer.Email, filesResponse.Commit.Committer.Email) 120 assert.EqualValues(t, expectedCreateFileResponse.Commit.Committer.Name, filesResponse.Commit.Committer.Name) 121 122 // test delete file 123 assert.Nil(t, filesResponse.Files[2]) 124 125 gitRepo.Close() 126 } 127 128 // Test changing files in a new branch 129 changeFilesOptions := getChangeFilesOptions() 130 changeFilesOptions.BranchName = repo1.DefaultBranch 131 changeFilesOptions.NewBranchName = "new_branch" 132 fileID++ 133 createTreePath := fmt.Sprintf("new/file%d.txt", fileID) 134 updateTreePath := fmt.Sprintf("update/file%d.txt", fileID) 135 deleteTreePath := fmt.Sprintf("delete/file%d.txt", fileID) 136 changeFilesOptions.Files[0].Path = createTreePath 137 changeFilesOptions.Files[1].Path = updateTreePath 138 changeFilesOptions.Files[2].Path = deleteTreePath 139 createFile(user2, repo1, updateTreePath) 140 createFile(user2, repo1, deleteTreePath) 141 url := fmt.Sprintf("/api/v1/repos/%s/%s/contents", user2.Name, repo1.Name) 142 req := NewRequestWithJSON(t, "POST", url, &changeFilesOptions). 143 AddTokenAuth(token2) 144 resp := MakeRequest(t, req, http.StatusCreated) 145 var filesResponse api.FilesResponse 146 DecodeJSON(t, resp, &filesResponse) 147 expectedCreateSHA := "a635aa942442ddfdba07468cf9661c08fbdf0ebf" 148 expectedCreateHTMLURL := fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/new_branch/new/file%d.txt", fileID) 149 expectedCreateDownloadURL := fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/new_branch/new/file%d.txt", fileID) 150 expectedUpdateSHA := "08bd14b2e2852529157324de9c226b3364e76136" 151 expectedUpdateHTMLURL := fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/new_branch/update/file%d.txt", fileID) 152 expectedUpdateDownloadURL := fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/new_branch/update/file%d.txt", fileID) 153 assert.EqualValues(t, expectedCreateSHA, filesResponse.Files[0].SHA) 154 assert.EqualValues(t, expectedCreateHTMLURL, *filesResponse.Files[0].HTMLURL) 155 assert.EqualValues(t, expectedCreateDownloadURL, *filesResponse.Files[0].DownloadURL) 156 assert.EqualValues(t, expectedUpdateSHA, filesResponse.Files[1].SHA) 157 assert.EqualValues(t, expectedUpdateHTMLURL, *filesResponse.Files[1].HTMLURL) 158 assert.EqualValues(t, expectedUpdateDownloadURL, *filesResponse.Files[1].DownloadURL) 159 assert.Nil(t, filesResponse.Files[2]) 160 161 assert.EqualValues(t, changeFilesOptions.Message+"\n", filesResponse.Commit.Message) 162 163 // Test updating a file and renaming it 164 changeFilesOptions = getChangeFilesOptions() 165 changeFilesOptions.BranchName = repo1.DefaultBranch 166 fileID++ 167 updateTreePath = fmt.Sprintf("update/file%d.txt", fileID) 168 createFile(user2, repo1, updateTreePath) 169 changeFilesOptions.Files = []*api.ChangeFileOperation{changeFilesOptions.Files[1]} 170 changeFilesOptions.Files[0].FromPath = updateTreePath 171 changeFilesOptions.Files[0].Path = "rename/" + updateTreePath 172 req = NewRequestWithJSON(t, "POST", url, &changeFilesOptions). 173 AddTokenAuth(token2) 174 resp = MakeRequest(t, req, http.StatusCreated) 175 DecodeJSON(t, resp, &filesResponse) 176 expectedUpdateSHA = "08bd14b2e2852529157324de9c226b3364e76136" 177 expectedUpdateHTMLURL = fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/master/rename/update/file%d.txt", fileID) 178 expectedUpdateDownloadURL = fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/master/rename/update/file%d.txt", fileID) 179 assert.EqualValues(t, expectedUpdateSHA, filesResponse.Files[0].SHA) 180 assert.EqualValues(t, expectedUpdateHTMLURL, *filesResponse.Files[0].HTMLURL) 181 assert.EqualValues(t, expectedUpdateDownloadURL, *filesResponse.Files[0].DownloadURL) 182 183 // Test updating a file without a message 184 changeFilesOptions = getChangeFilesOptions() 185 changeFilesOptions.Message = "" 186 changeFilesOptions.BranchName = repo1.DefaultBranch 187 fileID++ 188 createTreePath = fmt.Sprintf("new/file%d.txt", fileID) 189 updateTreePath = fmt.Sprintf("update/file%d.txt", fileID) 190 deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID) 191 changeFilesOptions.Files[0].Path = createTreePath 192 changeFilesOptions.Files[1].Path = updateTreePath 193 changeFilesOptions.Files[2].Path = deleteTreePath 194 createFile(user2, repo1, updateTreePath) 195 createFile(user2, repo1, deleteTreePath) 196 req = NewRequestWithJSON(t, "POST", url, &changeFilesOptions). 197 AddTokenAuth(token2) 198 resp = MakeRequest(t, req, http.StatusCreated) 199 DecodeJSON(t, resp, &filesResponse) 200 expectedMessage := fmt.Sprintf("Add %v\nUpdate %v\nDelete %v\n", createTreePath, updateTreePath, deleteTreePath) 201 assert.EqualValues(t, expectedMessage, filesResponse.Commit.Message) 202 203 // Test updating a file with the wrong SHA 204 fileID++ 205 updateTreePath = fmt.Sprintf("update/file%d.txt", fileID) 206 createFile(user2, repo1, updateTreePath) 207 changeFilesOptions = getChangeFilesOptions() 208 changeFilesOptions.Files = []*api.ChangeFileOperation{changeFilesOptions.Files[1]} 209 changeFilesOptions.Files[0].Path = updateTreePath 210 correctSHA := changeFilesOptions.Files[0].SHA 211 changeFilesOptions.Files[0].SHA = "badsha" 212 req = NewRequestWithJSON(t, "POST", url, &changeFilesOptions). 213 AddTokenAuth(token2) 214 resp = MakeRequest(t, req, http.StatusUnprocessableEntity) 215 expectedAPIError := context.APIError{ 216 Message: "sha does not match [given: " + changeFilesOptions.Files[0].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 createTreePath = fmt.Sprintf("new/file%d.txt", fileID) 226 updateTreePath = fmt.Sprintf("update/file%d.txt", fileID) 227 deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID) 228 createFile(user2, repo16, updateTreePath) 229 createFile(user2, repo16, deleteTreePath) 230 changeFilesOptions = getChangeFilesOptions() 231 changeFilesOptions.Files[0].Path = createTreePath 232 changeFilesOptions.Files[1].Path = updateTreePath 233 changeFilesOptions.Files[2].Path = deleteTreePath 234 req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents", user2.Name, repo16.Name), &changeFilesOptions). 235 AddTokenAuth(token4) 236 MakeRequest(t, req, http.StatusNotFound) 237 238 // Tests a repo with no token given so will fail 239 fileID++ 240 createTreePath = fmt.Sprintf("new/file%d.txt", fileID) 241 updateTreePath = fmt.Sprintf("update/file%d.txt", fileID) 242 deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID) 243 createFile(user2, repo16, updateTreePath) 244 createFile(user2, repo16, deleteTreePath) 245 changeFilesOptions = getChangeFilesOptions() 246 changeFilesOptions.Files[0].Path = createTreePath 247 changeFilesOptions.Files[1].Path = updateTreePath 248 changeFilesOptions.Files[2].Path = deleteTreePath 249 req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents", user2.Name, repo16.Name), &changeFilesOptions) 250 MakeRequest(t, req, http.StatusNotFound) 251 252 // Test using access token for a private repo that the user of the token owns 253 fileID++ 254 createTreePath = fmt.Sprintf("new/file%d.txt", fileID) 255 updateTreePath = fmt.Sprintf("update/file%d.txt", fileID) 256 deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID) 257 createFile(user2, repo16, updateTreePath) 258 createFile(user2, repo16, deleteTreePath) 259 changeFilesOptions = getChangeFilesOptions() 260 changeFilesOptions.Files[0].Path = createTreePath 261 changeFilesOptions.Files[1].Path = updateTreePath 262 changeFilesOptions.Files[2].Path = deleteTreePath 263 req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents", user2.Name, repo16.Name), &changeFilesOptions). 264 AddTokenAuth(token2) 265 MakeRequest(t, req, http.StatusCreated) 266 267 // Test using org repo "org3/repo3" where user2 is a collaborator 268 fileID++ 269 createTreePath = fmt.Sprintf("new/file%d.txt", fileID) 270 updateTreePath = fmt.Sprintf("update/file%d.txt", fileID) 271 deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID) 272 createFile(org3, repo3, updateTreePath) 273 createFile(org3, repo3, deleteTreePath) 274 changeFilesOptions = getChangeFilesOptions() 275 changeFilesOptions.Files[0].Path = createTreePath 276 changeFilesOptions.Files[1].Path = updateTreePath 277 changeFilesOptions.Files[2].Path = deleteTreePath 278 req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents", org3.Name, repo3.Name), &changeFilesOptions). 279 AddTokenAuth(token2) 280 MakeRequest(t, req, http.StatusCreated) 281 282 // Test using org repo "org3/repo3" with no user token 283 fileID++ 284 createTreePath = fmt.Sprintf("new/file%d.txt", fileID) 285 updateTreePath = fmt.Sprintf("update/file%d.txt", fileID) 286 deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID) 287 createFile(org3, repo3, updateTreePath) 288 createFile(org3, repo3, deleteTreePath) 289 changeFilesOptions = getChangeFilesOptions() 290 changeFilesOptions.Files[0].Path = createTreePath 291 changeFilesOptions.Files[1].Path = updateTreePath 292 changeFilesOptions.Files[2].Path = deleteTreePath 293 req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents", org3.Name, repo3.Name), &changeFilesOptions) 294 MakeRequest(t, req, http.StatusNotFound) 295 296 // Test using repo "user2/repo1" where user4 is a NOT collaborator 297 fileID++ 298 createTreePath = fmt.Sprintf("new/file%d.txt", fileID) 299 updateTreePath = fmt.Sprintf("update/file%d.txt", fileID) 300 deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID) 301 createFile(user2, repo1, updateTreePath) 302 createFile(user2, repo1, deleteTreePath) 303 changeFilesOptions = getChangeFilesOptions() 304 changeFilesOptions.Files[0].Path = createTreePath 305 changeFilesOptions.Files[1].Path = updateTreePath 306 changeFilesOptions.Files[2].Path = deleteTreePath 307 req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents", user2.Name, repo1.Name), &changeFilesOptions). 308 AddTokenAuth(token4) 309 MakeRequest(t, req, http.StatusForbidden) 310 }) 311 }