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