code.gitea.io/gitea@v1.21.7/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/context" 19 "code.gitea.io/gitea/modules/git" 20 "code.gitea.io/gitea/modules/setting" 21 api "code.gitea.io/gitea/modules/structs" 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 url := fmt.Sprintf("/api/v1/repos/%s/%s/contents?token=%s", user2.Name, repo1.Name, token2) 97 req := NewRequestWithJSON(t, "POST", url, &changeFilesOptions) 98 resp := MakeRequest(t, req, http.StatusCreated) 99 gitRepo, _ := git.OpenRepository(stdCtx.Background(), repo1.RepoPath()) 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?token=%s", user2.Name, repo1.Name, token2) 142 req := NewRequestWithJSON(t, "POST", url, &changeFilesOptions) 143 resp := MakeRequest(t, req, http.StatusCreated) 144 var filesResponse api.FilesResponse 145 DecodeJSON(t, resp, &filesResponse) 146 expectedCreateSHA := "a635aa942442ddfdba07468cf9661c08fbdf0ebf" 147 expectedCreateHTMLURL := fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/new_branch/new/file%d.txt", fileID) 148 expectedCreateDownloadURL := fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/new_branch/new/file%d.txt", fileID) 149 expectedUpdateSHA := "08bd14b2e2852529157324de9c226b3364e76136" 150 expectedUpdateHTMLURL := fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/new_branch/update/file%d.txt", fileID) 151 expectedUpdateDownloadURL := fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/new_branch/update/file%d.txt", fileID) 152 assert.EqualValues(t, expectedCreateSHA, filesResponse.Files[0].SHA) 153 assert.EqualValues(t, expectedCreateHTMLURL, *filesResponse.Files[0].HTMLURL) 154 assert.EqualValues(t, expectedCreateDownloadURL, *filesResponse.Files[0].DownloadURL) 155 assert.EqualValues(t, expectedUpdateSHA, filesResponse.Files[1].SHA) 156 assert.EqualValues(t, expectedUpdateHTMLURL, *filesResponse.Files[1].HTMLURL) 157 assert.EqualValues(t, expectedUpdateDownloadURL, *filesResponse.Files[1].DownloadURL) 158 assert.Nil(t, filesResponse.Files[2]) 159 160 assert.EqualValues(t, changeFilesOptions.Message+"\n", filesResponse.Commit.Message) 161 162 // Test updating a file and renaming it 163 changeFilesOptions = getChangeFilesOptions() 164 changeFilesOptions.BranchName = repo1.DefaultBranch 165 fileID++ 166 updateTreePath = fmt.Sprintf("update/file%d.txt", fileID) 167 createFile(user2, repo1, updateTreePath) 168 changeFilesOptions.Files = []*api.ChangeFileOperation{changeFilesOptions.Files[1]} 169 changeFilesOptions.Files[0].FromPath = updateTreePath 170 changeFilesOptions.Files[0].Path = "rename/" + updateTreePath 171 req = NewRequestWithJSON(t, "POST", url, &changeFilesOptions) 172 resp = MakeRequest(t, req, http.StatusCreated) 173 DecodeJSON(t, resp, &filesResponse) 174 expectedUpdateSHA = "08bd14b2e2852529157324de9c226b3364e76136" 175 expectedUpdateHTMLURL = fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/master/rename/update/file%d.txt", fileID) 176 expectedUpdateDownloadURL = fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/master/rename/update/file%d.txt", fileID) 177 assert.EqualValues(t, expectedUpdateSHA, filesResponse.Files[0].SHA) 178 assert.EqualValues(t, expectedUpdateHTMLURL, *filesResponse.Files[0].HTMLURL) 179 assert.EqualValues(t, expectedUpdateDownloadURL, *filesResponse.Files[0].DownloadURL) 180 181 // Test updating a file without a message 182 changeFilesOptions = getChangeFilesOptions() 183 changeFilesOptions.Message = "" 184 changeFilesOptions.BranchName = repo1.DefaultBranch 185 fileID++ 186 createTreePath = fmt.Sprintf("new/file%d.txt", fileID) 187 updateTreePath = fmt.Sprintf("update/file%d.txt", fileID) 188 deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID) 189 changeFilesOptions.Files[0].Path = createTreePath 190 changeFilesOptions.Files[1].Path = updateTreePath 191 changeFilesOptions.Files[2].Path = deleteTreePath 192 createFile(user2, repo1, updateTreePath) 193 createFile(user2, repo1, deleteTreePath) 194 req = NewRequestWithJSON(t, "POST", url, &changeFilesOptions) 195 resp = MakeRequest(t, req, http.StatusCreated) 196 DecodeJSON(t, resp, &filesResponse) 197 expectedMessage := fmt.Sprintf("Add %v\nUpdate %v\nDelete %v\n", createTreePath, updateTreePath, deleteTreePath) 198 assert.EqualValues(t, expectedMessage, filesResponse.Commit.Message) 199 200 // Test updating a file with the wrong SHA 201 fileID++ 202 updateTreePath = fmt.Sprintf("update/file%d.txt", fileID) 203 createFile(user2, repo1, updateTreePath) 204 changeFilesOptions = getChangeFilesOptions() 205 changeFilesOptions.Files = []*api.ChangeFileOperation{changeFilesOptions.Files[1]} 206 changeFilesOptions.Files[0].Path = updateTreePath 207 correctSHA := changeFilesOptions.Files[0].SHA 208 changeFilesOptions.Files[0].SHA = "badsha" 209 req = NewRequestWithJSON(t, "POST", url, &changeFilesOptions) 210 resp = MakeRequest(t, req, http.StatusUnprocessableEntity) 211 expectedAPIError := context.APIError{ 212 Message: "sha does not match [given: " + changeFilesOptions.Files[0].SHA + ", expected: " + correctSHA + "]", 213 URL: setting.API.SwaggerURL, 214 } 215 var apiError context.APIError 216 DecodeJSON(t, resp, &apiError) 217 assert.Equal(t, expectedAPIError, apiError) 218 219 // Test creating a file in repo1 by user4 who does not have write access 220 fileID++ 221 createTreePath = fmt.Sprintf("new/file%d.txt", fileID) 222 updateTreePath = fmt.Sprintf("update/file%d.txt", fileID) 223 deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID) 224 createFile(user2, repo16, updateTreePath) 225 createFile(user2, repo16, deleteTreePath) 226 changeFilesOptions = getChangeFilesOptions() 227 changeFilesOptions.Files[0].Path = createTreePath 228 changeFilesOptions.Files[1].Path = updateTreePath 229 changeFilesOptions.Files[2].Path = deleteTreePath 230 url = fmt.Sprintf("/api/v1/repos/%s/%s/contents?token=%s", user2.Name, repo16.Name, token4) 231 req = NewRequestWithJSON(t, "POST", url, &changeFilesOptions) 232 MakeRequest(t, req, http.StatusNotFound) 233 234 // Tests a repo with no token given so will fail 235 fileID++ 236 createTreePath = fmt.Sprintf("new/file%d.txt", fileID) 237 updateTreePath = fmt.Sprintf("update/file%d.txt", fileID) 238 deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID) 239 createFile(user2, repo16, updateTreePath) 240 createFile(user2, repo16, deleteTreePath) 241 changeFilesOptions = getChangeFilesOptions() 242 changeFilesOptions.Files[0].Path = createTreePath 243 changeFilesOptions.Files[1].Path = updateTreePath 244 changeFilesOptions.Files[2].Path = deleteTreePath 245 url = fmt.Sprintf("/api/v1/repos/%s/%s/contents", user2.Name, repo16.Name) 246 req = NewRequestWithJSON(t, "POST", url, &changeFilesOptions) 247 MakeRequest(t, req, http.StatusNotFound) 248 249 // Test using access token for a private repo that the user of the token owns 250 fileID++ 251 createTreePath = fmt.Sprintf("new/file%d.txt", fileID) 252 updateTreePath = fmt.Sprintf("update/file%d.txt", fileID) 253 deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID) 254 createFile(user2, repo16, updateTreePath) 255 createFile(user2, repo16, deleteTreePath) 256 changeFilesOptions = getChangeFilesOptions() 257 changeFilesOptions.Files[0].Path = createTreePath 258 changeFilesOptions.Files[1].Path = updateTreePath 259 changeFilesOptions.Files[2].Path = deleteTreePath 260 url = fmt.Sprintf("/api/v1/repos/%s/%s/contents?token=%s", user2.Name, repo16.Name, token2) 261 req = NewRequestWithJSON(t, "POST", url, &changeFilesOptions) 262 MakeRequest(t, req, http.StatusCreated) 263 264 // Test using org repo "org3/repo3" where user2 is a collaborator 265 fileID++ 266 createTreePath = fmt.Sprintf("new/file%d.txt", fileID) 267 updateTreePath = fmt.Sprintf("update/file%d.txt", fileID) 268 deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID) 269 createFile(org3, repo3, updateTreePath) 270 createFile(org3, repo3, deleteTreePath) 271 changeFilesOptions = getChangeFilesOptions() 272 changeFilesOptions.Files[0].Path = createTreePath 273 changeFilesOptions.Files[1].Path = updateTreePath 274 changeFilesOptions.Files[2].Path = deleteTreePath 275 url = fmt.Sprintf("/api/v1/repos/%s/%s/contents?token=%s", org3.Name, repo3.Name, token2) 276 req = NewRequestWithJSON(t, "POST", url, &changeFilesOptions) 277 MakeRequest(t, req, http.StatusCreated) 278 279 // Test using org repo "org3/repo3" with no user token 280 fileID++ 281 createTreePath = fmt.Sprintf("new/file%d.txt", fileID) 282 updateTreePath = fmt.Sprintf("update/file%d.txt", fileID) 283 deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID) 284 createFile(org3, repo3, updateTreePath) 285 createFile(org3, repo3, deleteTreePath) 286 changeFilesOptions = getChangeFilesOptions() 287 changeFilesOptions.Files[0].Path = createTreePath 288 changeFilesOptions.Files[1].Path = updateTreePath 289 changeFilesOptions.Files[2].Path = deleteTreePath 290 url = fmt.Sprintf("/api/v1/repos/%s/%s/contents", org3.Name, repo3.Name) 291 req = NewRequestWithJSON(t, "POST", url, &changeFilesOptions) 292 MakeRequest(t, req, http.StatusNotFound) 293 294 // Test using repo "user2/repo1" where user4 is a NOT collaborator 295 fileID++ 296 createTreePath = fmt.Sprintf("new/file%d.txt", fileID) 297 updateTreePath = fmt.Sprintf("update/file%d.txt", fileID) 298 deleteTreePath = fmt.Sprintf("delete/file%d.txt", fileID) 299 createFile(user2, repo1, updateTreePath) 300 createFile(user2, repo1, deleteTreePath) 301 changeFilesOptions = getChangeFilesOptions() 302 changeFilesOptions.Files[0].Path = createTreePath 303 changeFilesOptions.Files[1].Path = updateTreePath 304 changeFilesOptions.Files[2].Path = deleteTreePath 305 url = fmt.Sprintf("/api/v1/repos/%s/%s/contents?token=%s", user2.Name, repo1.Name, token4) 306 req = NewRequestWithJSON(t, "POST", url, &changeFilesOptions) 307 MakeRequest(t, req, http.StatusForbidden) 308 }) 309 }