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  }