code.gitea.io/gitea@v1.22.3/tests/integration/api_repo_test.go (about)

     1  // Copyright 2017 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  	access_model "code.gitea.io/gitea/models/perm/access"
    15  	repo_model "code.gitea.io/gitea/models/repo"
    16  	unit_model "code.gitea.io/gitea/models/unit"
    17  	"code.gitea.io/gitea/models/unittest"
    18  	user_model "code.gitea.io/gitea/models/user"
    19  	"code.gitea.io/gitea/modules/setting"
    20  	api "code.gitea.io/gitea/modules/structs"
    21  	repo_service "code.gitea.io/gitea/services/repository"
    22  	"code.gitea.io/gitea/tests"
    23  
    24  	"github.com/stretchr/testify/assert"
    25  )
    26  
    27  func TestAPIUserReposNotLogin(t *testing.T) {
    28  	defer tests.PrepareTestEnv(t)()
    29  	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
    30  
    31  	req := NewRequestf(t, "GET", "/api/v1/users/%s/repos", user.Name)
    32  	resp := MakeRequest(t, req, http.StatusOK)
    33  
    34  	var apiRepos []api.Repository
    35  	DecodeJSON(t, resp, &apiRepos)
    36  	expectedLen := unittest.GetCount(t, repo_model.Repository{OwnerID: user.ID},
    37  		unittest.Cond("is_private = ?", false))
    38  	assert.Len(t, apiRepos, expectedLen)
    39  	for _, repo := range apiRepos {
    40  		assert.EqualValues(t, user.ID, repo.Owner.ID)
    41  		assert.False(t, repo.Private)
    42  	}
    43  }
    44  
    45  func TestAPIUserReposWithWrongToken(t *testing.T) {
    46  	defer tests.PrepareTestEnv(t)()
    47  	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
    48  	wrongToken := fmt.Sprintf("Bearer %s", "wrong_token")
    49  	req := NewRequestf(t, "GET", "/api/v1/users/%s/repos", user.Name).
    50  		AddTokenAuth(wrongToken)
    51  	resp := MakeRequest(t, req, http.StatusUnauthorized)
    52  
    53  	assert.Contains(t, resp.Body.String(), "user does not exist")
    54  }
    55  
    56  func TestAPISearchRepo(t *testing.T) {
    57  	defer tests.PrepareTestEnv(t)()
    58  	const keyword = "test"
    59  
    60  	req := NewRequestf(t, "GET", "/api/v1/repos/search?q=%s", keyword)
    61  	resp := MakeRequest(t, req, http.StatusOK)
    62  
    63  	var body api.SearchResults
    64  	DecodeJSON(t, resp, &body)
    65  	assert.NotEmpty(t, body.Data)
    66  	for _, repo := range body.Data {
    67  		assert.Contains(t, repo.Name, keyword)
    68  		assert.False(t, repo.Private)
    69  	}
    70  
    71  	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 15})
    72  	user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 16})
    73  	org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 18})
    74  	user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 20})
    75  	orgUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 17})
    76  
    77  	oldAPIDefaultNum := setting.API.DefaultPagingNum
    78  	defer func() {
    79  		setting.API.DefaultPagingNum = oldAPIDefaultNum
    80  	}()
    81  	setting.API.DefaultPagingNum = 10
    82  
    83  	// Map of expected results, where key is user for login
    84  	type expectedResults map[*user_model.User]struct {
    85  		count           int
    86  		repoOwnerID     int64
    87  		repoName        string
    88  		includesPrivate bool
    89  	}
    90  
    91  	testCases := []struct {
    92  		name, requestURL string
    93  		expectedResults
    94  	}{
    95  		{
    96  			name: "RepositoriesMax50", requestURL: "/api/v1/repos/search?limit=50&private=false", expectedResults: expectedResults{
    97  				nil:   {count: 35},
    98  				user:  {count: 35},
    99  				user2: {count: 35},
   100  			},
   101  		},
   102  		{
   103  			name: "RepositoriesMax10", requestURL: "/api/v1/repos/search?limit=10&private=false", expectedResults: expectedResults{
   104  				nil:   {count: 10},
   105  				user:  {count: 10},
   106  				user2: {count: 10},
   107  			},
   108  		},
   109  		{
   110  			name: "RepositoriesDefault", requestURL: "/api/v1/repos/search?default&private=false", expectedResults: expectedResults{
   111  				nil:   {count: 10},
   112  				user:  {count: 10},
   113  				user2: {count: 10},
   114  			},
   115  		},
   116  		{
   117  			name: "RepositoriesByName", requestURL: fmt.Sprintf("/api/v1/repos/search?q=%s&private=false", "big_test_"), expectedResults: expectedResults{
   118  				nil:   {count: 7, repoName: "big_test_"},
   119  				user:  {count: 7, repoName: "big_test_"},
   120  				user2: {count: 7, repoName: "big_test_"},
   121  			},
   122  		},
   123  		{
   124  			name: "RepositoriesByName", requestURL: fmt.Sprintf("/api/v1/repos/search?q=%s&private=false", "user2/big_test_"), expectedResults: expectedResults{
   125  				user2: {count: 2, repoName: "big_test_"},
   126  			},
   127  		},
   128  		{
   129  			name: "RepositoriesAccessibleAndRelatedToUser", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", user.ID), expectedResults: expectedResults{
   130  				nil:   {count: 5},
   131  				user:  {count: 9, includesPrivate: true},
   132  				user2: {count: 6, includesPrivate: true},
   133  			},
   134  		},
   135  		{
   136  			name: "RepositoriesAccessibleAndRelatedToUser2", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", user2.ID), expectedResults: expectedResults{
   137  				nil:   {count: 1},
   138  				user:  {count: 2, includesPrivate: true},
   139  				user2: {count: 2, includesPrivate: true},
   140  				user4: {count: 1},
   141  			},
   142  		},
   143  		{
   144  			name: "RepositoriesAccessibleAndRelatedToUser3", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", org3.ID), expectedResults: expectedResults{
   145  				nil:   {count: 1},
   146  				user:  {count: 4, includesPrivate: true},
   147  				user2: {count: 3, includesPrivate: true},
   148  				org3:  {count: 4, includesPrivate: true},
   149  			},
   150  		},
   151  		{
   152  			name: "RepositoriesOwnedByOrganization", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", orgUser.ID), expectedResults: expectedResults{
   153  				nil:   {count: 1, repoOwnerID: orgUser.ID},
   154  				user:  {count: 2, repoOwnerID: orgUser.ID, includesPrivate: true},
   155  				user2: {count: 1, repoOwnerID: orgUser.ID},
   156  			},
   157  		},
   158  		{name: "RepositoriesAccessibleAndRelatedToUser4", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", user4.ID), expectedResults: expectedResults{
   159  			nil:   {count: 3},
   160  			user:  {count: 4, includesPrivate: true},
   161  			user4: {count: 7, includesPrivate: true},
   162  		}},
   163  		{name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeSource", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s", user4.ID, "source"), expectedResults: expectedResults{
   164  			nil:   {count: 0},
   165  			user:  {count: 1, includesPrivate: true},
   166  			user4: {count: 1, includesPrivate: true},
   167  		}},
   168  		{name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeFork", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s", user4.ID, "fork"), expectedResults: expectedResults{
   169  			nil:   {count: 1},
   170  			user:  {count: 1},
   171  			user4: {count: 2, includesPrivate: true},
   172  		}},
   173  		{name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeFork/Exclusive", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s&exclusive=1", user4.ID, "fork"), expectedResults: expectedResults{
   174  			nil:   {count: 1},
   175  			user:  {count: 1},
   176  			user4: {count: 2, includesPrivate: true},
   177  		}},
   178  		{name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeMirror", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s", user4.ID, "mirror"), expectedResults: expectedResults{
   179  			nil:   {count: 2},
   180  			user:  {count: 2},
   181  			user4: {count: 4, includesPrivate: true},
   182  		}},
   183  		{name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeMirror/Exclusive", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s&exclusive=1", user4.ID, "mirror"), expectedResults: expectedResults{
   184  			nil:   {count: 1},
   185  			user:  {count: 1},
   186  			user4: {count: 2, includesPrivate: true},
   187  		}},
   188  		{name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeCollaborative", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s", user4.ID, "collaborative"), expectedResults: expectedResults{
   189  			nil:   {count: 0},
   190  			user:  {count: 1, includesPrivate: true},
   191  			user4: {count: 1, includesPrivate: true},
   192  		}},
   193  	}
   194  
   195  	for _, testCase := range testCases {
   196  		t.Run(testCase.name, func(t *testing.T) {
   197  			for userToLogin, expected := range testCase.expectedResults {
   198  				var testName string
   199  				var userID int64
   200  				var token string
   201  				if userToLogin != nil && userToLogin.ID > 0 {
   202  					testName = fmt.Sprintf("LoggedUser%d", userToLogin.ID)
   203  					session := loginUser(t, userToLogin.Name)
   204  					token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository)
   205  					userID = userToLogin.ID
   206  				} else {
   207  					testName = "AnonymousUser"
   208  					_ = emptyTestSession(t)
   209  				}
   210  
   211  				t.Run(testName, func(t *testing.T) {
   212  					request := NewRequest(t, "GET", testCase.requestURL).
   213  						AddTokenAuth(token)
   214  					response := MakeRequest(t, request, http.StatusOK)
   215  
   216  					var body api.SearchResults
   217  					DecodeJSON(t, response, &body)
   218  
   219  					repoNames := make([]string, 0, len(body.Data))
   220  					for _, repo := range body.Data {
   221  						repoNames = append(repoNames, fmt.Sprintf("%d:%s:%t", repo.ID, repo.FullName, repo.Private))
   222  					}
   223  					assert.Len(t, repoNames, expected.count)
   224  					for _, repo := range body.Data {
   225  						r := getRepo(t, repo.ID)
   226  						hasAccess, err := access_model.HasAnyUnitAccess(db.DefaultContext, userID, r)
   227  						assert.NoError(t, err, "Error when checking if User: %d has access to %s: %v", userID, repo.FullName, err)
   228  						assert.True(t, hasAccess, "User: %d does not have access to %s", userID, repo.FullName)
   229  
   230  						assert.NotEmpty(t, repo.Name)
   231  						assert.Equal(t, repo.Name, r.Name)
   232  
   233  						if len(expected.repoName) > 0 {
   234  							assert.Contains(t, repo.Name, expected.repoName)
   235  						}
   236  
   237  						if expected.repoOwnerID > 0 {
   238  							assert.Equal(t, expected.repoOwnerID, repo.Owner.ID)
   239  						}
   240  
   241  						if !expected.includesPrivate {
   242  							assert.False(t, repo.Private, "User: %d not expecting private repository: %s", userID, repo.FullName)
   243  						}
   244  					}
   245  				})
   246  			}
   247  		})
   248  	}
   249  }
   250  
   251  var repoCache = make(map[int64]*repo_model.Repository)
   252  
   253  func getRepo(t *testing.T, repoID int64) *repo_model.Repository {
   254  	if _, ok := repoCache[repoID]; !ok {
   255  		repoCache[repoID] = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID})
   256  	}
   257  	return repoCache[repoID]
   258  }
   259  
   260  func TestAPIViewRepo(t *testing.T) {
   261  	defer tests.PrepareTestEnv(t)()
   262  
   263  	var repo api.Repository
   264  
   265  	req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1")
   266  	resp := MakeRequest(t, req, http.StatusOK)
   267  	DecodeJSON(t, resp, &repo)
   268  	assert.EqualValues(t, 1, repo.ID)
   269  	assert.EqualValues(t, "repo1", repo.Name)
   270  	assert.EqualValues(t, 2, repo.Releases)
   271  	assert.EqualValues(t, 1, repo.OpenIssues)
   272  	assert.EqualValues(t, 3, repo.OpenPulls)
   273  
   274  	req = NewRequest(t, "GET", "/api/v1/repos/user12/repo10")
   275  	resp = MakeRequest(t, req, http.StatusOK)
   276  	DecodeJSON(t, resp, &repo)
   277  	assert.EqualValues(t, 10, repo.ID)
   278  	assert.EqualValues(t, "repo10", repo.Name)
   279  	assert.EqualValues(t, 1, repo.OpenPulls)
   280  	assert.EqualValues(t, 1, repo.Forks)
   281  
   282  	req = NewRequest(t, "GET", "/api/v1/repos/user5/repo4")
   283  	resp = MakeRequest(t, req, http.StatusOK)
   284  	DecodeJSON(t, resp, &repo)
   285  	assert.EqualValues(t, 4, repo.ID)
   286  	assert.EqualValues(t, "repo4", repo.Name)
   287  	assert.EqualValues(t, 1, repo.Stars)
   288  }
   289  
   290  func TestAPIOrgRepos(t *testing.T) {
   291  	defer tests.PrepareTestEnv(t)()
   292  	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
   293  	user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
   294  	org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
   295  	// org3 is an Org. Check their repos.
   296  	sourceOrg := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
   297  
   298  	expectedResults := map[*user_model.User]struct {
   299  		count           int
   300  		includesPrivate bool
   301  	}{
   302  		user:  {count: 1},
   303  		user:  {count: 3, includesPrivate: true},
   304  		user2: {count: 3, includesPrivate: true},
   305  		org3:  {count: 1},
   306  	}
   307  
   308  	for userToLogin, expected := range expectedResults {
   309  		testName := fmt.Sprintf("LoggedUser%d", userToLogin.ID)
   310  		session := loginUser(t, userToLogin.Name)
   311  		token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadOrganization)
   312  
   313  		t.Run(testName, func(t *testing.T) {
   314  			req := NewRequestf(t, "GET", "/api/v1/orgs/%s/repos", sourceOrg.Name).
   315  				AddTokenAuth(token)
   316  			resp := MakeRequest(t, req, http.StatusOK)
   317  
   318  			var apiRepos []*api.Repository
   319  			DecodeJSON(t, resp, &apiRepos)
   320  			assert.Len(t, apiRepos, expected.count)
   321  			for _, repo := range apiRepos {
   322  				if !expected.includesPrivate {
   323  					assert.False(t, repo.Private)
   324  				}
   325  			}
   326  		})
   327  	}
   328  }
   329  
   330  // See issue #28483. Tests to make sure we consider more than just code unit-enabled repositories.
   331  func TestAPIOrgReposWithCodeUnitDisabled(t *testing.T) {
   332  	defer tests.PrepareTestEnv(t)()
   333  	repo21 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: "repo21"})
   334  	org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo21.OwnerID})
   335  
   336  	// Disable code repository unit.
   337  	var units []unit_model.Type
   338  	units = append(units, unit_model.TypeCode)
   339  
   340  	if err := repo_service.UpdateRepositoryUnits(db.DefaultContext, repo21, nil, units); err != nil {
   341  		assert.Fail(t, "should have been able to delete code repository unit; failed to %v", err)
   342  	}
   343  	assert.False(t, repo21.UnitEnabled(db.DefaultContext, unit_model.TypeCode))
   344  
   345  	session := loginUser(t, "user2")
   346  	token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadOrganization)
   347  
   348  	req := NewRequestf(t, "GET", "/api/v1/orgs/%s/repos", org3.Name).
   349  		AddTokenAuth(token)
   350  
   351  	resp := MakeRequest(t, req, http.StatusOK)
   352  	var apiRepos []*api.Repository
   353  	DecodeJSON(t, resp, &apiRepos)
   354  
   355  	var repoNames []string
   356  	for _, r := range apiRepos {
   357  		repoNames = append(repoNames, r.Name)
   358  	}
   359  
   360  	assert.Contains(t, repoNames, repo21.Name)
   361  }
   362  
   363  func TestAPIGetRepoByIDUnauthorized(t *testing.T) {
   364  	defer tests.PrepareTestEnv(t)()
   365  	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
   366  	session := loginUser(t, user.Name)
   367  	token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository)
   368  	req := NewRequest(t, "GET", "/api/v1/repositories/2").
   369  		AddTokenAuth(token)
   370  	MakeRequest(t, req, http.StatusNotFound)
   371  }
   372  
   373  func TestAPIRepoMigrate(t *testing.T) {
   374  	testCases := []struct {
   375  		ctxUserID, userID  int64
   376  		cloneURL, repoName string
   377  		expectedStatus     int
   378  	}{
   379  		{ctxUserID: 1, userID: 2, cloneURL: "https://github.com/go-gitea/test_repo.git", repoName: "git-admin", expectedStatus: http.StatusCreated},
   380  		{ctxUserID: 2, userID: 2, cloneURL: "https://github.com/go-gitea/test_repo.git", repoName: "git-own", expectedStatus: http.StatusCreated},
   381  		{ctxUserID: 2, userID: 1, cloneURL: "https://github.com/go-gitea/test_repo.git", repoName: "git-bad", expectedStatus: http.StatusForbidden},
   382  		{ctxUserID: 2, userID: 3, cloneURL: "https://github.com/go-gitea/test_repo.git", repoName: "git-org", expectedStatus: http.StatusCreated},
   383  		{ctxUserID: 2, userID: 6, cloneURL: "https://github.com/go-gitea/test_repo.git", repoName: "git-bad-org", expectedStatus: http.StatusForbidden},
   384  		{ctxUserID: 2, userID: 3, cloneURL: "https://localhost:3000/user/test_repo.git", repoName: "private-ip", expectedStatus: http.StatusUnprocessableEntity},
   385  		{ctxUserID: 2, userID: 3, cloneURL: "https://10.0.0.1/user/test_repo.git", repoName: "private-ip", expectedStatus: http.StatusUnprocessableEntity},
   386  	}
   387  
   388  	defer tests.PrepareTestEnv(t)()
   389  	for _, testCase := range testCases {
   390  		user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: testCase.ctxUserID})
   391  		session := loginUser(t, user.Name)
   392  		token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
   393  		req := NewRequestWithJSON(t, "POST", "/api/v1/repos/migrate", &api.MigrateRepoOptions{
   394  			CloneAddr:   testCase.cloneURL,
   395  			RepoOwnerID: testCase.userID,
   396  			RepoName:    testCase.repoName,
   397  		}).AddTokenAuth(token)
   398  		resp := MakeRequest(t, req, NoExpectedStatus)
   399  		if resp.Code == http.StatusUnprocessableEntity {
   400  			respJSON := map[string]string{}
   401  			DecodeJSON(t, resp, &respJSON)
   402  			switch respJSON["message"] {
   403  			case "Remote visit addressed rate limitation.":
   404  				t.Log("test hit github rate limitation")
   405  			case "You can not import from disallowed hosts.":
   406  				assert.EqualValues(t, "private-ip", testCase.repoName)
   407  			default:
   408  				assert.FailNow(t, "unexpected error '%v' on url '%s'", respJSON["message"], testCase.cloneURL)
   409  			}
   410  		} else {
   411  			assert.EqualValues(t, testCase.expectedStatus, resp.Code)
   412  		}
   413  	}
   414  }
   415  
   416  func TestAPIRepoMigrateConflict(t *testing.T) {
   417  	onGiteaRun(t, testAPIRepoMigrateConflict)
   418  }
   419  
   420  func testAPIRepoMigrateConflict(t *testing.T, u *url.URL) {
   421  	username := "user2"
   422  	baseAPITestContext := NewAPITestContext(t, username, "repo1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
   423  
   424  	u.Path = baseAPITestContext.GitPath()
   425  
   426  	t.Run("Existing", func(t *testing.T) {
   427  		httpContext := baseAPITestContext
   428  
   429  		httpContext.Reponame = "repo-tmp-17"
   430  		t.Run("CreateRepo", doAPICreateRepository(httpContext, false))
   431  
   432  		user, err := user_model.GetUserByName(db.DefaultContext, httpContext.Username)
   433  		assert.NoError(t, err)
   434  		userID := user.ID
   435  
   436  		cloneURL := "https://github.com/go-gitea/test_repo.git"
   437  
   438  		req := NewRequestWithJSON(t, "POST", "/api/v1/repos/migrate",
   439  			&api.MigrateRepoOptions{
   440  				CloneAddr:   cloneURL,
   441  				RepoOwnerID: userID,
   442  				RepoName:    httpContext.Reponame,
   443  			}).
   444  			AddTokenAuth(httpContext.Token)
   445  		resp := httpContext.Session.MakeRequest(t, req, http.StatusConflict)
   446  		respJSON := map[string]string{}
   447  		DecodeJSON(t, resp, &respJSON)
   448  		assert.Equal(t, "The repository with the same name already exists.", respJSON["message"])
   449  	})
   450  }
   451  
   452  // mirror-sync must fail with "400 (Bad Request)" when an attempt is made to
   453  // sync a non-mirror repository.
   454  func TestAPIMirrorSyncNonMirrorRepo(t *testing.T) {
   455  	defer tests.PrepareTestEnv(t)()
   456  
   457  	session := loginUser(t, "user2")
   458  	token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
   459  
   460  	var repo api.Repository
   461  	req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1")
   462  	resp := MakeRequest(t, req, http.StatusOK)
   463  	DecodeJSON(t, resp, &repo)
   464  	assert.False(t, repo.Mirror)
   465  
   466  	req = NewRequestf(t, "POST", "/api/v1/repos/user2/repo1/mirror-sync").
   467  		AddTokenAuth(token)
   468  	resp = MakeRequest(t, req, http.StatusBadRequest)
   469  	errRespJSON := map[string]string{}
   470  	DecodeJSON(t, resp, &errRespJSON)
   471  	assert.Equal(t, "Repository is not a mirror", errRespJSON["message"])
   472  }
   473  
   474  func TestAPIOrgRepoCreate(t *testing.T) {
   475  	testCases := []struct {
   476  		ctxUserID         int64
   477  		orgName, repoName string
   478  		expectedStatus    int
   479  	}{
   480  		{ctxUserID: 1, orgName: "org3", repoName: "repo-admin", expectedStatus: http.StatusCreated},
   481  		{ctxUserID: 2, orgName: "org3", repoName: "repo-own", expectedStatus: http.StatusCreated},
   482  		{ctxUserID: 2, orgName: "org6", repoName: "repo-bad-org", expectedStatus: http.StatusForbidden},
   483  		{ctxUserID: 28, orgName: "org3", repoName: "repo-creator", expectedStatus: http.StatusCreated},
   484  		{ctxUserID: 28, orgName: "org6", repoName: "repo-not-creator", expectedStatus: http.StatusForbidden},
   485  	}
   486  
   487  	defer tests.PrepareTestEnv(t)()
   488  	for _, testCase := range testCases {
   489  		user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: testCase.ctxUserID})
   490  		session := loginUser(t, user.Name)
   491  		token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization, auth_model.AccessTokenScopeWriteRepository)
   492  		req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/org/%s/repos", testCase.orgName), &api.CreateRepoOption{
   493  			Name: testCase.repoName,
   494  		}).AddTokenAuth(token)
   495  		MakeRequest(t, req, testCase.expectedStatus)
   496  	}
   497  }
   498  
   499  func TestAPIRepoCreateConflict(t *testing.T) {
   500  	onGiteaRun(t, testAPIRepoCreateConflict)
   501  }
   502  
   503  func testAPIRepoCreateConflict(t *testing.T, u *url.URL) {
   504  	username := "user2"
   505  	baseAPITestContext := NewAPITestContext(t, username, "repo1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
   506  
   507  	u.Path = baseAPITestContext.GitPath()
   508  
   509  	t.Run("Existing", func(t *testing.T) {
   510  		httpContext := baseAPITestContext
   511  
   512  		httpContext.Reponame = "repo-tmp-17"
   513  		t.Run("CreateRepo", doAPICreateRepository(httpContext, false))
   514  
   515  		req := NewRequestWithJSON(t, "POST", "/api/v1/user/repos",
   516  			&api.CreateRepoOption{
   517  				Name: httpContext.Reponame,
   518  			}).
   519  			AddTokenAuth(httpContext.Token)
   520  		resp := httpContext.Session.MakeRequest(t, req, http.StatusConflict)
   521  		respJSON := map[string]string{}
   522  		DecodeJSON(t, resp, &respJSON)
   523  		assert.Equal(t, "The repository with the same name already exists.", respJSON["message"])
   524  	})
   525  }
   526  
   527  func TestAPIRepoTransfer(t *testing.T) {
   528  	testCases := []struct {
   529  		ctxUserID      int64
   530  		newOwner       string
   531  		teams          *[]int64
   532  		expectedStatus int
   533  	}{
   534  		// Disclaimer for test story: "user1" is an admin, "user2" is normal user and part of in owner team of org "org3"
   535  		// Transfer to a user with teams in another org should fail
   536  		{ctxUserID: 1, newOwner: "org3", teams: &[]int64{5}, expectedStatus: http.StatusForbidden},
   537  		// Transfer to a user with non-existent team IDs should fail
   538  		{ctxUserID: 1, newOwner: "user2", teams: &[]int64{2}, expectedStatus: http.StatusUnprocessableEntity},
   539  		// Transfer should go through
   540  		{ctxUserID: 1, newOwner: "org3", teams: &[]int64{2}, expectedStatus: http.StatusAccepted},
   541  		// Let user transfer it back to himself
   542  		{ctxUserID: 2, newOwner: "user2", expectedStatus: http.StatusAccepted},
   543  		// And revert transfer
   544  		{ctxUserID: 2, newOwner: "org3", teams: &[]int64{2}, expectedStatus: http.StatusAccepted},
   545  		// Cannot start transfer to an existing repo
   546  		{ctxUserID: 2, newOwner: "org3", teams: nil, expectedStatus: http.StatusUnprocessableEntity},
   547  		// Start transfer, repo is now in pending transfer mode
   548  		{ctxUserID: 2, newOwner: "org6", teams: nil, expectedStatus: http.StatusCreated},
   549  	}
   550  
   551  	defer tests.PrepareTestEnv(t)()
   552  
   553  	// create repo to move
   554  	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
   555  	session := loginUser(t, user.Name)
   556  	token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
   557  	repoName := "moveME"
   558  	apiRepo := new(api.Repository)
   559  	req := NewRequestWithJSON(t, "POST", "/api/v1/user/repos", &api.CreateRepoOption{
   560  		Name:        repoName,
   561  		Description: "repo move around",
   562  		Private:     false,
   563  		Readme:      "Default",
   564  		AutoInit:    true,
   565  	}).AddTokenAuth(token)
   566  	resp := MakeRequest(t, req, http.StatusCreated)
   567  	DecodeJSON(t, resp, apiRepo)
   568  
   569  	// start testing
   570  	for _, testCase := range testCases {
   571  		user = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: testCase.ctxUserID})
   572  		repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID})
   573  		session = loginUser(t, user.Name)
   574  		token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
   575  		req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer", repo.OwnerName, repo.Name), &api.TransferRepoOption{
   576  			NewOwner: testCase.newOwner,
   577  			TeamIDs:  testCase.teams,
   578  		}).AddTokenAuth(token)
   579  		MakeRequest(t, req, testCase.expectedStatus)
   580  	}
   581  
   582  	// cleanup
   583  	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID})
   584  	_ = repo_service.DeleteRepositoryDirectly(db.DefaultContext, user, repo.ID)
   585  }
   586  
   587  func transfer(t *testing.T) *repo_model.Repository {
   588  	// create repo to move
   589  	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
   590  	session := loginUser(t, user.Name)
   591  	token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
   592  	repoName := "moveME"
   593  	apiRepo := new(api.Repository)
   594  	req := NewRequestWithJSON(t, "POST", "/api/v1/user/repos", &api.CreateRepoOption{
   595  		Name:        repoName,
   596  		Description: "repo move around",
   597  		Private:     false,
   598  		Readme:      "Default",
   599  		AutoInit:    true,
   600  	}).AddTokenAuth(token)
   601  
   602  	resp := MakeRequest(t, req, http.StatusCreated)
   603  	DecodeJSON(t, resp, apiRepo)
   604  
   605  	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID})
   606  	req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer", repo.OwnerName, repo.Name), &api.TransferRepoOption{
   607  		NewOwner: "user4",
   608  	}).AddTokenAuth(token)
   609  	MakeRequest(t, req, http.StatusCreated)
   610  
   611  	return repo
   612  }
   613  
   614  func TestAPIAcceptTransfer(t *testing.T) {
   615  	defer tests.PrepareTestEnv(t)()
   616  
   617  	repo := transfer(t)
   618  
   619  	// try to accept with not authorized user
   620  	session := loginUser(t, "user2")
   621  	token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
   622  	req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer/reject", repo.OwnerName, repo.Name)).
   623  		AddTokenAuth(token)
   624  	MakeRequest(t, req, http.StatusForbidden)
   625  
   626  	// try to accept repo that's not marked as transferred
   627  	req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer/accept", "user2", "repo1")).
   628  		AddTokenAuth(token)
   629  	MakeRequest(t, req, http.StatusNotFound)
   630  
   631  	// accept transfer
   632  	session = loginUser(t, "user4")
   633  	token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
   634  
   635  	req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer/accept", repo.OwnerName, repo.Name)).
   636  		AddTokenAuth(token)
   637  	resp := MakeRequest(t, req, http.StatusAccepted)
   638  	apiRepo := new(api.Repository)
   639  	DecodeJSON(t, resp, apiRepo)
   640  	assert.Equal(t, "user4", apiRepo.Owner.UserName)
   641  }
   642  
   643  func TestAPIRejectTransfer(t *testing.T) {
   644  	defer tests.PrepareTestEnv(t)()
   645  
   646  	repo := transfer(t)
   647  
   648  	// try to reject with not authorized user
   649  	session := loginUser(t, "user2")
   650  	token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
   651  	req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer/reject", repo.OwnerName, repo.Name)).
   652  		AddTokenAuth(token)
   653  	MakeRequest(t, req, http.StatusForbidden)
   654  
   655  	// try to reject repo that's not marked as transferred
   656  	req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer/reject", "user2", "repo1")).
   657  		AddTokenAuth(token)
   658  	MakeRequest(t, req, http.StatusNotFound)
   659  
   660  	// reject transfer
   661  	session = loginUser(t, "user4")
   662  	token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
   663  
   664  	req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer/reject", repo.OwnerName, repo.Name)).
   665  		AddTokenAuth(token)
   666  	resp := MakeRequest(t, req, http.StatusOK)
   667  	apiRepo := new(api.Repository)
   668  	DecodeJSON(t, resp, apiRepo)
   669  	assert.Equal(t, "user2", apiRepo.Owner.UserName)
   670  }
   671  
   672  func TestAPIGenerateRepo(t *testing.T) {
   673  	defer tests.PrepareTestEnv(t)()
   674  
   675  	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
   676  	session := loginUser(t, user.Name)
   677  	token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
   678  
   679  	templateRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 44})
   680  
   681  	// user
   682  	repo := new(api.Repository)
   683  	req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/generate", templateRepo.OwnerName, templateRepo.Name), &api.GenerateRepoOption{
   684  		Owner:       user.Name,
   685  		Name:        "new-repo",
   686  		Description: "test generate repo",
   687  		Private:     false,
   688  		GitContent:  true,
   689  	}).AddTokenAuth(token)
   690  	resp := MakeRequest(t, req, http.StatusCreated)
   691  	DecodeJSON(t, resp, repo)
   692  
   693  	assert.Equal(t, "new-repo", repo.Name)
   694  
   695  	// org
   696  	req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/generate", templateRepo.OwnerName, templateRepo.Name), &api.GenerateRepoOption{
   697  		Owner:       "org3",
   698  		Name:        "new-repo",
   699  		Description: "test generate repo",
   700  		Private:     false,
   701  		GitContent:  true,
   702  	}).AddTokenAuth(token)
   703  	resp = MakeRequest(t, req, http.StatusCreated)
   704  	DecodeJSON(t, resp, repo)
   705  
   706  	assert.Equal(t, "new-repo", repo.Name)
   707  }
   708  
   709  func TestAPIRepoGetReviewers(t *testing.T) {
   710  	defer tests.PrepareTestEnv(t)()
   711  	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
   712  	session := loginUser(t, user.Name)
   713  	token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository)
   714  	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
   715  
   716  	req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/reviewers", user.Name, repo.Name).
   717  		AddTokenAuth(token)
   718  	resp := MakeRequest(t, req, http.StatusOK)
   719  	var reviewers []*api.User
   720  	DecodeJSON(t, resp, &reviewers)
   721  	if assert.Len(t, reviewers, 3) {
   722  		assert.ElementsMatch(t, []int64{1, 4, 11}, []int64{reviewers[0].ID, reviewers[1].ID, reviewers[2].ID})
   723  	}
   724  }
   725  
   726  func TestAPIRepoGetAssignees(t *testing.T) {
   727  	defer tests.PrepareTestEnv(t)()
   728  	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
   729  	session := loginUser(t, user.Name)
   730  	token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository)
   731  	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
   732  
   733  	req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/assignees", user.Name, repo.Name).
   734  		AddTokenAuth(token)
   735  	resp := MakeRequest(t, req, http.StatusOK)
   736  	var assignees []*api.User
   737  	DecodeJSON(t, resp, &assignees)
   738  	assert.Len(t, assignees, 1)
   739  }