// Copyright 2019 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package integrations import ( "fmt" "net/http" "net/url" "testing" "code.gitea.io/gitea/models" api "code.gitea.io/gitea/modules/structs" "github.com/stretchr/testify/assert" ) // getRepoEditOptionFromRepo gets the options for an existing repo exactly as is func getRepoEditOptionFromRepo(repo *models.Repository) *api.EditRepoOption { name := repo.Name description := repo.Description website := repo.Website private := repo.IsPrivate hasIssues := false var internalTracker *api.InternalTracker var externalTracker *api.ExternalTracker if unit, err := repo.GetUnit(models.UnitTypeIssues); err == nil { config := unit.IssuesConfig() hasIssues = true internalTracker = &api.InternalTracker{ EnableTimeTracker: config.EnableTimetracker, AllowOnlyContributorsToTrackTime: config.AllowOnlyContributorsToTrackTime, EnableIssueDependencies: config.EnableDependencies, } } else if unit, err := repo.GetUnit(models.UnitTypeExternalTracker); err == nil { config := unit.ExternalTrackerConfig() hasIssues = true externalTracker = &api.ExternalTracker{ ExternalTrackerURL: config.ExternalTrackerURL, ExternalTrackerFormat: config.ExternalTrackerFormat, ExternalTrackerStyle: config.ExternalTrackerStyle, } } hasWiki := false var externalWiki *api.ExternalWiki if _, err := repo.GetUnit(models.UnitTypeWiki); err == nil { hasWiki = true } else if unit, err := repo.GetUnit(models.UnitTypeExternalWiki); err == nil { hasWiki = true config := unit.ExternalWikiConfig() externalWiki = &api.ExternalWiki{ ExternalWikiURL: config.ExternalWikiURL, } } defaultBranch := repo.DefaultBranch hasPullRequests := false ignoreWhitespaceConflicts := false allowMerge := false allowRebase := false allowRebaseMerge := false allowSquash := false if unit, err := repo.GetUnit(models.UnitTypePullRequests); err == nil { config := unit.PullRequestsConfig() hasPullRequests = true ignoreWhitespaceConflicts = config.IgnoreWhitespaceConflicts allowMerge = config.AllowMerge allowRebase = config.AllowRebase allowRebaseMerge = config.AllowRebaseMerge allowSquash = config.AllowSquash } archived := repo.IsArchived return &api.EditRepoOption{ Name: &name, Description: &description, Website: &website, Private: &private, HasIssues: &hasIssues, ExternalTracker: externalTracker, InternalTracker: internalTracker, HasWiki: &hasWiki, ExternalWiki: externalWiki, DefaultBranch: &defaultBranch, HasPullRequests: &hasPullRequests, IgnoreWhitespaceConflicts: &ignoreWhitespaceConflicts, AllowMerge: &allowMerge, AllowRebase: &allowRebase, AllowRebaseMerge: &allowRebaseMerge, AllowSquash: &allowSquash, Archived: &archived, } } // getNewRepoEditOption Gets the options to change everything about an existing repo by adding to strings or changing // the boolean func getNewRepoEditOption(opts *api.EditRepoOption) *api.EditRepoOption { // Gives a new property to everything name := *opts.Name + "renamed" description := "new description" website := "http://wwww.newwebsite.com" private := !*opts.Private hasIssues := !*opts.HasIssues hasWiki := !*opts.HasWiki defaultBranch := "master" hasPullRequests := !*opts.HasPullRequests ignoreWhitespaceConflicts := !*opts.IgnoreWhitespaceConflicts allowMerge := !*opts.AllowMerge allowRebase := !*opts.AllowRebase allowRebaseMerge := !*opts.AllowRebaseMerge allowSquash := !*opts.AllowSquash archived := !*opts.Archived return &api.EditRepoOption{ Name: &name, Description: &description, Website: &website, Private: &private, DefaultBranch: &defaultBranch, HasIssues: &hasIssues, HasWiki: &hasWiki, HasPullRequests: &hasPullRequests, IgnoreWhitespaceConflicts: &ignoreWhitespaceConflicts, AllowMerge: &allowMerge, AllowRebase: &allowRebase, AllowRebaseMerge: &allowRebaseMerge, AllowSquash: &allowSquash, Archived: &archived, } } func TestAPIRepoEdit(t *testing.T) { onGiteaRun(t, func(t *testing.T, u *url.URL) { bFalse, bTrue := false, true user2 := models.AssertExistsAndLoadBean(t, &models.User{ID: 2}).(*models.User) // owner of the repo1 & repo16 user3 := models.AssertExistsAndLoadBean(t, &models.User{ID: 3}).(*models.User) // owner of the repo3, is an org user4 := models.AssertExistsAndLoadBean(t, &models.User{ID: 4}).(*models.User) // owner of neither repos repo1 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) // public repo repo3 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 3}).(*models.Repository) // public repo repo15 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 15}).(*models.Repository) // empty repo repo16 := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository) // private repo // Get user2's token session := loginUser(t, user2.Name) token2 := getTokenForLoggedInUser(t, session) session = emptyTestSession(t) // Get user4's token session = loginUser(t, user4.Name) token4 := getTokenForLoggedInUser(t, session) session = emptyTestSession(t) // Test editing a repo1 which user2 owns, changing name and many properties origRepoEditOption := getRepoEditOptionFromRepo(repo1) repoEditOption := getNewRepoEditOption(origRepoEditOption) url := fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, repo1.Name, token2) req := NewRequestWithJSON(t, "PATCH", url, &repoEditOption) resp := session.MakeRequest(t, req, http.StatusOK) var repo api.Repository DecodeJSON(t, resp, &repo) assert.NotNil(t, repo) // check response assert.Equal(t, *repoEditOption.Name, repo.Name) assert.Equal(t, *repoEditOption.Description, repo.Description) assert.Equal(t, *repoEditOption.Website, repo.Website) assert.Equal(t, *repoEditOption.Archived, repo.Archived) // check repo1 from database repo1edited := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) repo1editedOption := getRepoEditOptionFromRepo(repo1edited) assert.Equal(t, *repoEditOption.Name, *repo1editedOption.Name) assert.Equal(t, *repoEditOption.Description, *repo1editedOption.Description) assert.Equal(t, *repoEditOption.Website, *repo1editedOption.Website) assert.Equal(t, *repoEditOption.Archived, *repo1editedOption.Archived) assert.Equal(t, *repoEditOption.Private, *repo1editedOption.Private) assert.Equal(t, *repoEditOption.HasWiki, *repo1editedOption.HasWiki) //Test editing repo1 to use internal issue and wiki (default) *repoEditOption.HasIssues = true repoEditOption.ExternalTracker = nil repoEditOption.InternalTracker = &api.InternalTracker{ EnableTimeTracker: false, AllowOnlyContributorsToTrackTime: false, EnableIssueDependencies: false, } *repoEditOption.HasWiki = true repoEditOption.ExternalWiki = nil url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, *repoEditOption.Name, token2) req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) resp = session.MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &repo) assert.NotNil(t, repo) // check repo1 was written to database repo1edited = models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) repo1editedOption = getRepoEditOptionFromRepo(repo1edited) assert.Equal(t, *repo1editedOption.HasIssues, true) assert.Nil(t, repo1editedOption.ExternalTracker) assert.Equal(t, *repo1editedOption.InternalTracker, *repoEditOption.InternalTracker) assert.Equal(t, *repo1editedOption.HasWiki, true) assert.Nil(t, repo1editedOption.ExternalWiki) //Test editing repo1 to use external issue and wiki repoEditOption.ExternalTracker = &api.ExternalTracker{ ExternalTrackerURL: "http://www.somewebsite.com", ExternalTrackerFormat: "http://www.somewebsite.com/{user}/{repo}?issue={index}", ExternalTrackerStyle: "alphanumeric", } repoEditOption.ExternalWiki = &api.ExternalWiki{ ExternalWikiURL: "http://www.somewebsite.com", } req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) resp = session.MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &repo) assert.NotNil(t, repo) // check repo1 was written to database repo1edited = models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) repo1editedOption = getRepoEditOptionFromRepo(repo1edited) assert.Equal(t, *repo1editedOption.HasIssues, true) assert.Equal(t, *repo1editedOption.ExternalTracker, *repoEditOption.ExternalTracker) assert.Equal(t, *repo1editedOption.HasWiki, true) assert.Equal(t, *repo1editedOption.ExternalWiki, *repoEditOption.ExternalWiki) // Do some tests with invalid URL for external tracker and wiki repoEditOption.ExternalTracker.ExternalTrackerURL = "htp://www.somewebsite.com" req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) resp = session.MakeRequest(t, req, http.StatusUnprocessableEntity) repoEditOption.ExternalTracker.ExternalTrackerURL = "http://www.somewebsite.com" repoEditOption.ExternalTracker.ExternalTrackerFormat = "http://www.somewebsite.com/{user/{repo}?issue={index}" req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) resp = session.MakeRequest(t, req, http.StatusUnprocessableEntity) repoEditOption.ExternalTracker.ExternalTrackerFormat = "http://www.somewebsite.com/{user}/{repo}?issue={index}" repoEditOption.ExternalWiki.ExternalWikiURL = "htp://www.somewebsite.com" req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) resp = session.MakeRequest(t, req, http.StatusUnprocessableEntity) //Test small repo change through API with issue and wiki option not set; They shall not be touched. *repoEditOption.Description = "small change" repoEditOption.HasIssues = nil repoEditOption.ExternalTracker = nil repoEditOption.HasWiki = nil repoEditOption.ExternalWiki = nil req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) resp = session.MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &repo) assert.NotNil(t, repo) // check repo1 was written to database repo1edited = models.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) repo1editedOption = getRepoEditOptionFromRepo(repo1edited) assert.Equal(t, *repo1editedOption.Description, *repoEditOption.Description) assert.Equal(t, *repo1editedOption.HasIssues, true) assert.NotNil(t, *repo1editedOption.ExternalTracker) assert.Equal(t, *repo1editedOption.HasWiki, true) assert.NotNil(t, *repo1editedOption.ExternalWiki) // reset repo in db url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, *repoEditOption.Name, token2) req = NewRequestWithJSON(t, "PATCH", url, &origRepoEditOption) _ = session.MakeRequest(t, req, http.StatusOK) // Test editing a non-existing repo name := "repodoesnotexist" url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, name, token2) req = NewRequestWithJSON(t, "PATCH", url, &api.EditRepoOption{Name: &name}) _ = session.MakeRequest(t, req, http.StatusNotFound) // Test editing repo16 by user4 who does not have write access origRepoEditOption = getRepoEditOptionFromRepo(repo16) repoEditOption = getNewRepoEditOption(origRepoEditOption) url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, repo16.Name, token4) req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) session.MakeRequest(t, req, http.StatusNotFound) // Tests a repo with no token given so will fail origRepoEditOption = getRepoEditOptionFromRepo(repo16) repoEditOption = getNewRepoEditOption(origRepoEditOption) url = fmt.Sprintf("/api/v1/repos/%s/%s", user2.Name, repo16.Name) req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) _ = session.MakeRequest(t, req, http.StatusNotFound) // Test using access token for a private repo that the user of the token owns origRepoEditOption = getRepoEditOptionFromRepo(repo16) repoEditOption = getNewRepoEditOption(origRepoEditOption) url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, repo16.Name, token2) req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) _ = session.MakeRequest(t, req, http.StatusOK) // reset repo in db url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, *repoEditOption.Name, token2) req = NewRequestWithJSON(t, "PATCH", url, &origRepoEditOption) _ = session.MakeRequest(t, req, http.StatusOK) // Test making a repo public that is private repo16 = models.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository) assert.True(t, repo16.IsPrivate) repoEditOption = &api.EditRepoOption{ Private: &bFalse, } url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, repo16.Name, token2) req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) _ = session.MakeRequest(t, req, http.StatusOK) repo16 = models.AssertExistsAndLoadBean(t, &models.Repository{ID: 16}).(*models.Repository) assert.False(t, repo16.IsPrivate) // Make it private again repoEditOption.Private = &bTrue req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) _ = session.MakeRequest(t, req, http.StatusOK) // Test to change empty repo assert.False(t, repo15.IsArchived) url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, repo15.Name, token2) req = NewRequestWithJSON(t, "PATCH", url, &api.EditRepoOption{ Archived: &bTrue, }) _ = session.MakeRequest(t, req, http.StatusOK) repo15 = models.AssertExistsAndLoadBean(t, &models.Repository{ID: 15}).(*models.Repository) assert.True(t, repo15.IsArchived) req = NewRequestWithJSON(t, "PATCH", url, &api.EditRepoOption{ Archived: &bFalse, }) _ = session.MakeRequest(t, req, http.StatusOK) // Test using org repo "user3/repo3" where user2 is a collaborator origRepoEditOption = getRepoEditOptionFromRepo(repo3) repoEditOption = getNewRepoEditOption(origRepoEditOption) url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user3.Name, repo3.Name, token2) req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) session.MakeRequest(t, req, http.StatusOK) // reset repo in db url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user3.Name, *repoEditOption.Name, token2) req = NewRequestWithJSON(t, "PATCH", url, &origRepoEditOption) _ = session.MakeRequest(t, req, http.StatusOK) // Test using org repo "user3/repo3" with no user token origRepoEditOption = getRepoEditOptionFromRepo(repo3) repoEditOption = getNewRepoEditOption(origRepoEditOption) url = fmt.Sprintf("/api/v1/repos/%s/%s", user3.Name, repo3.Name) req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) session.MakeRequest(t, req, http.StatusNotFound) // Test using repo "user2/repo1" where user4 is a NOT collaborator origRepoEditOption = getRepoEditOptionFromRepo(repo1) repoEditOption = getNewRepoEditOption(origRepoEditOption) url = fmt.Sprintf("/api/v1/repos/%s/%s?token=%s", user2.Name, repo1.Name, token4) req = NewRequestWithJSON(t, "PATCH", url, &repoEditOption) session.MakeRequest(t, req, http.StatusForbidden) }) }