code.gitea.io/gitea@v1.21.7/tests/integration/api_repo_edit_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 "fmt" 8 "net/http" 9 "net/url" 10 "testing" 11 12 auth_model "code.gitea.io/gitea/models/auth" 13 "code.gitea.io/gitea/models/db" 14 repo_model "code.gitea.io/gitea/models/repo" 15 unit_model "code.gitea.io/gitea/models/unit" 16 "code.gitea.io/gitea/models/unittest" 17 user_model "code.gitea.io/gitea/models/user" 18 api "code.gitea.io/gitea/modules/structs" 19 20 "github.com/stretchr/testify/assert" 21 ) 22 23 // getRepoEditOptionFromRepo gets the options for an existing repo exactly as is 24 func getRepoEditOptionFromRepo(repo *repo_model.Repository) *api.EditRepoOption { 25 name := repo.Name 26 description := repo.Description 27 website := repo.Website 28 private := repo.IsPrivate 29 hasIssues := false 30 var internalTracker *api.InternalTracker 31 var externalTracker *api.ExternalTracker 32 if unit, err := repo.GetUnit(db.DefaultContext, unit_model.TypeIssues); err == nil { 33 config := unit.IssuesConfig() 34 hasIssues = true 35 internalTracker = &api.InternalTracker{ 36 EnableTimeTracker: config.EnableTimetracker, 37 AllowOnlyContributorsToTrackTime: config.AllowOnlyContributorsToTrackTime, 38 EnableIssueDependencies: config.EnableDependencies, 39 } 40 } else if unit, err := repo.GetUnit(db.DefaultContext, unit_model.TypeExternalTracker); err == nil { 41 config := unit.ExternalTrackerConfig() 42 hasIssues = true 43 externalTracker = &api.ExternalTracker{ 44 ExternalTrackerURL: config.ExternalTrackerURL, 45 ExternalTrackerFormat: config.ExternalTrackerFormat, 46 ExternalTrackerStyle: config.ExternalTrackerStyle, 47 ExternalTrackerRegexpPattern: config.ExternalTrackerRegexpPattern, 48 } 49 } 50 hasWiki := false 51 var externalWiki *api.ExternalWiki 52 if _, err := repo.GetUnit(db.DefaultContext, unit_model.TypeWiki); err == nil { 53 hasWiki = true 54 } else if unit, err := repo.GetUnit(db.DefaultContext, unit_model.TypeExternalWiki); err == nil { 55 hasWiki = true 56 config := unit.ExternalWikiConfig() 57 externalWiki = &api.ExternalWiki{ 58 ExternalWikiURL: config.ExternalWikiURL, 59 } 60 } 61 defaultBranch := repo.DefaultBranch 62 hasPullRequests := false 63 ignoreWhitespaceConflicts := false 64 allowMerge := false 65 allowRebase := false 66 allowRebaseMerge := false 67 allowSquash := false 68 if unit, err := repo.GetUnit(db.DefaultContext, unit_model.TypePullRequests); err == nil { 69 config := unit.PullRequestsConfig() 70 hasPullRequests = true 71 ignoreWhitespaceConflicts = config.IgnoreWhitespaceConflicts 72 allowMerge = config.AllowMerge 73 allowRebase = config.AllowRebase 74 allowRebaseMerge = config.AllowRebaseMerge 75 allowSquash = config.AllowSquash 76 } 77 archived := repo.IsArchived 78 return &api.EditRepoOption{ 79 Name: &name, 80 Description: &description, 81 Website: &website, 82 Private: &private, 83 HasIssues: &hasIssues, 84 ExternalTracker: externalTracker, 85 InternalTracker: internalTracker, 86 HasWiki: &hasWiki, 87 ExternalWiki: externalWiki, 88 DefaultBranch: &defaultBranch, 89 HasPullRequests: &hasPullRequests, 90 IgnoreWhitespaceConflicts: &ignoreWhitespaceConflicts, 91 AllowMerge: &allowMerge, 92 AllowRebase: &allowRebase, 93 AllowRebaseMerge: &allowRebaseMerge, 94 AllowSquash: &allowSquash, 95 Archived: &archived, 96 } 97 } 98 99 // getNewRepoEditOption Gets the options to change everything about an existing repo by adding to strings or changing 100 // the boolean 101 func getNewRepoEditOption(opts *api.EditRepoOption) *api.EditRepoOption { 102 // Gives a new property to everything 103 name := *opts.Name + "renamed" 104 description := "new description" 105 website := "http://wwww.newwebsite.com" 106 private := !*opts.Private 107 hasIssues := !*opts.HasIssues 108 hasWiki := !*opts.HasWiki 109 defaultBranch := "master" 110 hasPullRequests := !*opts.HasPullRequests 111 ignoreWhitespaceConflicts := !*opts.IgnoreWhitespaceConflicts 112 allowMerge := !*opts.AllowMerge 113 allowRebase := !*opts.AllowRebase 114 allowRebaseMerge := !*opts.AllowRebaseMerge 115 allowSquash := !*opts.AllowSquash 116 archived := !*opts.Archived 117 118 return &api.EditRepoOption{ 119 Name: &name, 120 Description: &description, 121 Website: &website, 122 Private: &private, 123 DefaultBranch: &defaultBranch, 124 HasIssues: &hasIssues, 125 HasWiki: &hasWiki, 126 HasPullRequests: &hasPullRequests, 127 IgnoreWhitespaceConflicts: &ignoreWhitespaceConflicts, 128 AllowMerge: &allowMerge, 129 AllowRebase: &allowRebase, 130 AllowRebaseMerge: &allowRebaseMerge, 131 AllowSquash: &allowSquash, 132 Archived: &archived, 133 } 134 } 135 136 func TestAPIRepoEdit(t *testing.T) { 137 onGiteaRun(t, func(t *testing.T, u *url.URL) { 138 bFalse, bTrue := false, true 139 140 user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // owner of the repo1 & repo16 141 org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}) // owner of the repo3, is an org 142 user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) // owner of neither repos 143 repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) // public repo 144 repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) // public repo 145 repo15 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 15}) // empty repo 146 repo16 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 16}) // private repo 147 148 // Get user2's token 149 session := loginUser(t, user2.Name) 150 token2 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) 151 // Get user4's token 152 session = loginUser(t, user4.Name) 153 token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) 154 155 // Test editing a repo1 which user2 owns, changing name and many properties 156 origRepoEditOption := getRepoEditOptionFromRepo(repo1) 157 repoEditOption := getNewRepoEditOption(origRepoEditOption) 158 url := fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, repo1.Name, token2) 159 req := NewRequestWithJSON(t, "PATCH", url, &repoEditOption) 160 resp := MakeRequest(t, req, http.StatusOK) 161 var repo api.Repository 162 DecodeJSON(t, resp, &repo) 163 assert.NotNil(t, repo) 164 // check response 165 assert.Equal(t, *repoEditOption.Name, repo.Name) 166 assert.Equal(t, *repoEditOption.Description, repo.Description) 167 assert.Equal(t, *repoEditOption.Website, repo.Website) 168 assert.Equal(t, *repoEditOption.Archived, repo.Archived) 169 // check repo1 from database 170 repo1edited := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) 171 repo1editedOption := getRepoEditOptionFromRepo(repo1edited) 172 assert.Equal(t, *repoEditOption.Name, *repo1editedOption.Name) 173 assert.Equal(t, *repoEditOption.Description, *repo1editedOption.Description) 174 assert.Equal(t, *repoEditOption.Website, *repo1editedOption.Website) 175 assert.Equal(t, *repoEditOption.Archived, *repo1editedOption.Archived) 176 assert.Equal(t, *repoEditOption.Private, *repo1editedOption.Private) 177 assert.Equal(t, *repoEditOption.HasWiki, *repo1editedOption.HasWiki) 178 179 // Test editing repo1 to use internal issue and wiki (default) 180 *repoEditOption.HasIssues = true 181 repoEditOption.ExternalTracker = nil 182 repoEditOption.InternalTracker = &api.InternalTracker{ 183 EnableTimeTracker: false, 184 AllowOnlyContributorsToTrackTime: false, 185 EnableIssueDependencies: false, 186 } 187 *repoEditOption.HasWiki = true 188 repoEditOption.ExternalWiki = nil 189 url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, *repoEditOption.Name, token2) 190 req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) 191 resp = MakeRequest(t, req, http.StatusOK) 192 DecodeJSON(t, resp, &repo) 193 assert.NotNil(t, repo) 194 // check repo1 was written to database 195 repo1edited = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) 196 repo1editedOption = getRepoEditOptionFromRepo(repo1edited) 197 assert.True(t, *repo1editedOption.HasIssues) 198 assert.Nil(t, repo1editedOption.ExternalTracker) 199 assert.Equal(t, *repo1editedOption.InternalTracker, *repoEditOption.InternalTracker) 200 assert.True(t, *repo1editedOption.HasWiki) 201 assert.Nil(t, repo1editedOption.ExternalWiki) 202 203 // Test editing repo1 to use external issue and wiki 204 repoEditOption.ExternalTracker = &api.ExternalTracker{ 205 ExternalTrackerURL: "http://www.somewebsite.com", 206 ExternalTrackerFormat: "http://www.somewebsite.com/{user}/{repo}?issue={index}", 207 ExternalTrackerStyle: "alphanumeric", 208 } 209 repoEditOption.ExternalWiki = &api.ExternalWiki{ 210 ExternalWikiURL: "http://www.somewebsite.com", 211 } 212 req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) 213 resp = MakeRequest(t, req, http.StatusOK) 214 DecodeJSON(t, resp, &repo) 215 assert.NotNil(t, repo) 216 // check repo1 was written to database 217 repo1edited = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) 218 repo1editedOption = getRepoEditOptionFromRepo(repo1edited) 219 assert.True(t, *repo1editedOption.HasIssues) 220 assert.Equal(t, *repo1editedOption.ExternalTracker, *repoEditOption.ExternalTracker) 221 assert.True(t, *repo1editedOption.HasWiki) 222 assert.Equal(t, *repo1editedOption.ExternalWiki, *repoEditOption.ExternalWiki) 223 224 repoEditOption.ExternalTracker.ExternalTrackerStyle = "regexp" 225 repoEditOption.ExternalTracker.ExternalTrackerRegexpPattern = `(\d+)` 226 req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) 227 resp = MakeRequest(t, req, http.StatusOK) 228 DecodeJSON(t, resp, &repo) 229 assert.NotNil(t, repo) 230 repo1edited = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) 231 repo1editedOption = getRepoEditOptionFromRepo(repo1edited) 232 assert.True(t, *repo1editedOption.HasIssues) 233 assert.Equal(t, *repo1editedOption.ExternalTracker, *repoEditOption.ExternalTracker) 234 235 // Do some tests with invalid URL for external tracker and wiki 236 repoEditOption.ExternalTracker.ExternalTrackerURL = "htp://www.somewebsite.com" 237 req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) 238 MakeRequest(t, req, http.StatusUnprocessableEntity) 239 repoEditOption.ExternalTracker.ExternalTrackerURL = "http://www.somewebsite.com" 240 repoEditOption.ExternalTracker.ExternalTrackerFormat = "http://www.somewebsite.com/{user/{repo}?issue={index}" 241 req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) 242 MakeRequest(t, req, http.StatusUnprocessableEntity) 243 repoEditOption.ExternalTracker.ExternalTrackerFormat = "http://www.somewebsite.com/{user}/{repo}?issue={index}" 244 repoEditOption.ExternalWiki.ExternalWikiURL = "htp://www.somewebsite.com" 245 req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) 246 MakeRequest(t, req, http.StatusUnprocessableEntity) 247 248 // Test small repo change through API with issue and wiki option not set; They shall not be touched. 249 *repoEditOption.Description = "small change" 250 repoEditOption.HasIssues = nil 251 repoEditOption.ExternalTracker = nil 252 repoEditOption.HasWiki = nil 253 repoEditOption.ExternalWiki = nil 254 req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) 255 resp = MakeRequest(t, req, http.StatusOK) 256 DecodeJSON(t, resp, &repo) 257 assert.NotNil(t, repo) 258 // check repo1 was written to database 259 repo1edited = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) 260 repo1editedOption = getRepoEditOptionFromRepo(repo1edited) 261 assert.Equal(t, *repo1editedOption.Description, *repoEditOption.Description) 262 assert.True(t, *repo1editedOption.HasIssues) 263 assert.NotNil(t, *repo1editedOption.ExternalTracker) 264 assert.True(t, *repo1editedOption.HasWiki) 265 assert.NotNil(t, *repo1editedOption.ExternalWiki) 266 267 // reset repo in db 268 url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, *repoEditOption.Name, token2) 269 req = NewRequestWithJSON(t, "PATCH", url, &origRepoEditOption) 270 _ = MakeRequest(t, req, http.StatusOK) 271 272 // Test editing a non-existing repo 273 name := "repodoesnotexist" 274 url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, name, token2) 275 req = NewRequestWithJSON(t, "PATCH", url, &api.EditRepoOption{Name: &name}) 276 _ = MakeRequest(t, req, http.StatusNotFound) 277 278 // Test editing repo16 by user4 who does not have write access 279 origRepoEditOption = getRepoEditOptionFromRepo(repo16) 280 repoEditOption = getNewRepoEditOption(origRepoEditOption) 281 url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, repo16.Name, token4) 282 req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) 283 MakeRequest(t, req, http.StatusNotFound) 284 285 // Tests a repo with no token given so will fail 286 origRepoEditOption = getRepoEditOptionFromRepo(repo16) 287 repoEditOption = getNewRepoEditOption(origRepoEditOption) 288 url = fmt.Sprintf("/api/v1/repos/%s/%s", user2.Name, repo16.Name) 289 req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) 290 _ = MakeRequest(t, req, http.StatusNotFound) 291 292 // Test using access token for a private repo that the user of the token owns 293 origRepoEditOption = getRepoEditOptionFromRepo(repo16) 294 repoEditOption = getNewRepoEditOption(origRepoEditOption) 295 url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, repo16.Name, token2) 296 req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) 297 _ = MakeRequest(t, req, http.StatusOK) 298 // reset repo in db 299 url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, *repoEditOption.Name, token2) 300 req = NewRequestWithJSON(t, "PATCH", url, &origRepoEditOption) 301 _ = MakeRequest(t, req, http.StatusOK) 302 303 // Test making a repo public that is private 304 repo16 = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 16}) 305 assert.True(t, repo16.IsPrivate) 306 repoEditOption = &api.EditRepoOption{ 307 Private: &bFalse, 308 } 309 url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, repo16.Name, token2) 310 req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) 311 _ = MakeRequest(t, req, http.StatusOK) 312 repo16 = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 16}) 313 assert.False(t, repo16.IsPrivate) 314 // Make it private again 315 repoEditOption.Private = &bTrue 316 req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) 317 _ = MakeRequest(t, req, http.StatusOK) 318 319 // Test to change empty repo 320 assert.False(t, repo15.IsArchived) 321 url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, repo15.Name, token2) 322 req = NewRequestWithJSON(t, "PATCH", url, &api.EditRepoOption{ 323 Archived: &bTrue, 324 }) 325 _ = MakeRequest(t, req, http.StatusOK) 326 repo15 = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 15}) 327 assert.True(t, repo15.IsArchived) 328 req = NewRequestWithJSON(t, "PATCH", url, &api.EditRepoOption{ 329 Archived: &bFalse, 330 }) 331 _ = MakeRequest(t, req, http.StatusOK) 332 333 // Test using org repo "org3/repo3" where user2 is a collaborator 334 origRepoEditOption = getRepoEditOptionFromRepo(repo3) 335 repoEditOption = getNewRepoEditOption(origRepoEditOption) 336 url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", org3.Name, repo3.Name, token2) 337 req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) 338 MakeRequest(t, req, http.StatusOK) 339 // reset repo in db 340 url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", org3.Name, *repoEditOption.Name, token2) 341 req = NewRequestWithJSON(t, "PATCH", url, &origRepoEditOption) 342 _ = MakeRequest(t, req, http.StatusOK) 343 344 // Test using org repo "org3/repo3" with no user token 345 origRepoEditOption = getRepoEditOptionFromRepo(repo3) 346 repoEditOption = getNewRepoEditOption(origRepoEditOption) 347 url = fmt.Sprintf("/api/v1/repos/%s/%s", org3.Name, repo3.Name) 348 req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) 349 MakeRequest(t, req, http.StatusNotFound) 350 351 // Test using repo "user2/repo1" where user4 is a NOT collaborator 352 origRepoEditOption = getRepoEditOptionFromRepo(repo1) 353 repoEditOption = getNewRepoEditOption(origRepoEditOption) 354 url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, repo1.Name, token4) 355 req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) 356 MakeRequest(t, req, http.StatusForbidden) 357 }) 358 }