gitea/tests/integration/api_repo_test.go
Bruno Sofiato 900ac62251
Allow code search by filename (#32210)
This is a large and complex PR, so let me explain in detail its changes.

First, I had to create new index mappings for Bleve and ElasticSerach as
the current ones do not support search by filename. This requires Gitea
to recreate the code search indexes (I do not know if this is a breaking
change, but I feel it deserves a heads-up).

I've used [this
approach](https://www.elastic.co/guide/en/elasticsearch/reference/7.17/analysis-pathhierarchy-tokenizer.html)
to model the filename index. It allows us to efficiently search for both
the full path and the name of a file. Bleve, however, does not support
this out-of-box, so I had to code a brand new [token
filter](https://blevesearch.com/docs/Token-Filters/) to generate the
search terms.

I also did an overhaul in the `indexer_test.go` file. It now asserts the
order of the expected results (this is important since matches based on
the name of a file are more relevant than those based on its content).
I've added new test scenarios that deal with searching by filename. They
use a new repo included in the Gitea fixture.

The screenshot below depicts how Gitea shows the search results. It
shows results based on content in the same way as the current version
does. In matches based on the filename, the first seven lines of the
file contents are shown (BTW, this is how GitHub does it).


![image](https://github.com/user-attachments/assets/9d938d86-1a8d-4f89-8644-1921a473e858)

Resolves #32096

---------

Signed-off-by: Bruno Sofiato <bruno.sofiato@gmail.com>
2024-10-11 23:35:04 +00:00

740 lines
28 KiB
Go

// Copyright 2017 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package integration
import (
"fmt"
"net/http"
"net/url"
"testing"
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
unit_model "code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
repo_service "code.gitea.io/gitea/services/repository"
"code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert"
)
func TestAPIUserReposNotLogin(t *testing.T) {
defer tests.PrepareTestEnv(t)()
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
req := NewRequestf(t, "GET", "/api/v1/users/%s/repos", user.Name)
resp := MakeRequest(t, req, http.StatusOK)
var apiRepos []api.Repository
DecodeJSON(t, resp, &apiRepos)
expectedLen := unittest.GetCount(t, repo_model.Repository{OwnerID: user.ID},
unittest.Cond("is_private = ?", false))
assert.Len(t, apiRepos, expectedLen)
for _, repo := range apiRepos {
assert.EqualValues(t, user.ID, repo.Owner.ID)
assert.False(t, repo.Private)
}
}
func TestAPIUserReposWithWrongToken(t *testing.T) {
defer tests.PrepareTestEnv(t)()
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
wrongToken := fmt.Sprintf("Bearer %s", "wrong_token")
req := NewRequestf(t, "GET", "/api/v1/users/%s/repos", user.Name).
AddTokenAuth(wrongToken)
resp := MakeRequest(t, req, http.StatusUnauthorized)
assert.Contains(t, resp.Body.String(), "user does not exist")
}
func TestAPISearchRepo(t *testing.T) {
defer tests.PrepareTestEnv(t)()
const keyword = "test"
req := NewRequestf(t, "GET", "/api/v1/repos/search?q=%s", keyword)
resp := MakeRequest(t, req, http.StatusOK)
var body api.SearchResults
DecodeJSON(t, resp, &body)
assert.NotEmpty(t, body.Data)
for _, repo := range body.Data {
assert.Contains(t, repo.Name, keyword)
assert.False(t, repo.Private)
}
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 15})
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 16})
org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 18})
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 20})
orgUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 17})
oldAPIDefaultNum := setting.API.DefaultPagingNum
defer func() {
setting.API.DefaultPagingNum = oldAPIDefaultNum
}()
setting.API.DefaultPagingNum = 10
// Map of expected results, where key is user for login
type expectedResults map[*user_model.User]struct {
count int
repoOwnerID int64
repoName string
includesPrivate bool
}
testCases := []struct {
name, requestURL string
expectedResults
}{
{
name: "RepositoriesMax50", requestURL: "/api/v1/repos/search?limit=50&private=false", expectedResults: expectedResults{
nil: {count: 36},
user: {count: 36},
user2: {count: 36},
},
},
{
name: "RepositoriesMax10", requestURL: "/api/v1/repos/search?limit=10&private=false", expectedResults: expectedResults{
nil: {count: 10},
user: {count: 10},
user2: {count: 10},
},
},
{
name: "RepositoriesDefault", requestURL: "/api/v1/repos/search?default&private=false", expectedResults: expectedResults{
nil: {count: 10},
user: {count: 10},
user2: {count: 10},
},
},
{
name: "RepositoriesByName", requestURL: fmt.Sprintf("/api/v1/repos/search?q=%s&private=false", "big_test_"), expectedResults: expectedResults{
nil: {count: 7, repoName: "big_test_"},
user: {count: 7, repoName: "big_test_"},
user2: {count: 7, repoName: "big_test_"},
},
},
{
name: "RepositoriesByName", requestURL: fmt.Sprintf("/api/v1/repos/search?q=%s&private=false", "user2/big_test_"), expectedResults: expectedResults{
user2: {count: 2, repoName: "big_test_"},
},
},
{
name: "RepositoriesAccessibleAndRelatedToUser", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", user.ID), expectedResults: expectedResults{
nil: {count: 5},
user: {count: 9, includesPrivate: true},
user2: {count: 6, includesPrivate: true},
},
},
{
name: "RepositoriesAccessibleAndRelatedToUser2", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", user2.ID), expectedResults: expectedResults{
nil: {count: 1},
user: {count: 2, includesPrivate: true},
user2: {count: 2, includesPrivate: true},
user4: {count: 1},
},
},
{
name: "RepositoriesAccessibleAndRelatedToUser3", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", org3.ID), expectedResults: expectedResults{
nil: {count: 1},
user: {count: 4, includesPrivate: true},
user2: {count: 3, includesPrivate: true},
org3: {count: 4, includesPrivate: true},
},
},
{
name: "RepositoriesOwnedByOrganization", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", orgUser.ID), expectedResults: expectedResults{
nil: {count: 1, repoOwnerID: orgUser.ID},
user: {count: 2, repoOwnerID: orgUser.ID, includesPrivate: true},
user2: {count: 1, repoOwnerID: orgUser.ID},
},
},
{name: "RepositoriesAccessibleAndRelatedToUser4", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d", user4.ID), expectedResults: expectedResults{
nil: {count: 3},
user: {count: 4, includesPrivate: true},
user4: {count: 7, includesPrivate: true},
}},
{name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeSource", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s", user4.ID, "source"), expectedResults: expectedResults{
nil: {count: 0},
user: {count: 1, includesPrivate: true},
user4: {count: 1, includesPrivate: true},
}},
{name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeFork", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s", user4.ID, "fork"), expectedResults: expectedResults{
nil: {count: 1},
user: {count: 1},
user4: {count: 2, includesPrivate: true},
}},
{name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeFork/Exclusive", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s&exclusive=1", user4.ID, "fork"), expectedResults: expectedResults{
nil: {count: 1},
user: {count: 1},
user4: {count: 2, includesPrivate: true},
}},
{name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeMirror", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s", user4.ID, "mirror"), expectedResults: expectedResults{
nil: {count: 2},
user: {count: 2},
user4: {count: 4, includesPrivate: true},
}},
{name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeMirror/Exclusive", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s&exclusive=1", user4.ID, "mirror"), expectedResults: expectedResults{
nil: {count: 1},
user: {count: 1},
user4: {count: 2, includesPrivate: true},
}},
{name: "RepositoriesAccessibleAndRelatedToUser4/SearchModeCollaborative", requestURL: fmt.Sprintf("/api/v1/repos/search?uid=%d&mode=%s", user4.ID, "collaborative"), expectedResults: expectedResults{
nil: {count: 0},
user: {count: 1, includesPrivate: true},
user4: {count: 1, includesPrivate: true},
}},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
for userToLogin, expected := range testCase.expectedResults {
var testName string
var userID int64
var token string
if userToLogin != nil && userToLogin.ID > 0 {
testName = fmt.Sprintf("LoggedUser%d", userToLogin.ID)
session := loginUser(t, userToLogin.Name)
token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository)
userID = userToLogin.ID
} else {
testName = "AnonymousUser"
_ = emptyTestSession(t)
}
t.Run(testName, func(t *testing.T) {
request := NewRequest(t, "GET", testCase.requestURL).
AddTokenAuth(token)
response := MakeRequest(t, request, http.StatusOK)
var body api.SearchResults
DecodeJSON(t, response, &body)
repoNames := make([]string, 0, len(body.Data))
for _, repo := range body.Data {
repoNames = append(repoNames, fmt.Sprintf("%d:%s:%t", repo.ID, repo.FullName, repo.Private))
}
assert.Len(t, repoNames, expected.count)
for _, repo := range body.Data {
r := getRepo(t, repo.ID)
hasAccess, err := access_model.HasAnyUnitAccess(db.DefaultContext, userID, r)
assert.NoError(t, err, "Error when checking if User: %d has access to %s: %v", userID, repo.FullName, err)
assert.True(t, hasAccess, "User: %d does not have access to %s", userID, repo.FullName)
assert.NotEmpty(t, repo.Name)
assert.Equal(t, repo.Name, r.Name)
if len(expected.repoName) > 0 {
assert.Contains(t, repo.Name, expected.repoName)
}
if expected.repoOwnerID > 0 {
assert.Equal(t, expected.repoOwnerID, repo.Owner.ID)
}
if !expected.includesPrivate {
assert.False(t, repo.Private, "User: %d not expecting private repository: %s", userID, repo.FullName)
}
}
})
}
})
}
}
var repoCache = make(map[int64]*repo_model.Repository)
func getRepo(t *testing.T, repoID int64) *repo_model.Repository {
if _, ok := repoCache[repoID]; !ok {
repoCache[repoID] = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repoID})
}
return repoCache[repoID]
}
func TestAPIViewRepo(t *testing.T) {
defer tests.PrepareTestEnv(t)()
var repo api.Repository
req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1")
resp := MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &repo)
assert.EqualValues(t, 1, repo.ID)
assert.EqualValues(t, "repo1", repo.Name)
assert.EqualValues(t, 2, repo.Releases)
assert.EqualValues(t, 1, repo.OpenIssues)
assert.EqualValues(t, 3, repo.OpenPulls)
req = NewRequest(t, "GET", "/api/v1/repos/user12/repo10")
resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &repo)
assert.EqualValues(t, 10, repo.ID)
assert.EqualValues(t, "repo10", repo.Name)
assert.EqualValues(t, 1, repo.OpenPulls)
assert.EqualValues(t, 1, repo.Forks)
req = NewRequest(t, "GET", "/api/v1/repos/user5/repo4")
resp = MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &repo)
assert.EqualValues(t, 4, repo.ID)
assert.EqualValues(t, "repo4", repo.Name)
assert.EqualValues(t, 1, repo.Stars)
}
func TestAPIOrgRepos(t *testing.T) {
defer tests.PrepareTestEnv(t)()
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
// org3 is an Org. Check their repos.
sourceOrg := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
expectedResults := map[*user_model.User]struct {
count int
includesPrivate bool
}{
user: {count: 1},
user: {count: 3, includesPrivate: true},
user2: {count: 3, includesPrivate: true},
org3: {count: 1},
}
for userToLogin, expected := range expectedResults {
testName := fmt.Sprintf("LoggedUser%d", userToLogin.ID)
session := loginUser(t, userToLogin.Name)
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadOrganization)
t.Run(testName, func(t *testing.T) {
req := NewRequestf(t, "GET", "/api/v1/orgs/%s/repos", sourceOrg.Name).
AddTokenAuth(token)
resp := MakeRequest(t, req, http.StatusOK)
var apiRepos []*api.Repository
DecodeJSON(t, resp, &apiRepos)
assert.Len(t, apiRepos, expected.count)
for _, repo := range apiRepos {
if !expected.includesPrivate {
assert.False(t, repo.Private)
}
}
})
}
}
// See issue #28483. Tests to make sure we consider more than just code unit-enabled repositories.
func TestAPIOrgReposWithCodeUnitDisabled(t *testing.T) {
defer tests.PrepareTestEnv(t)()
repo21 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: "repo21"})
org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo21.OwnerID})
// Disable code repository unit.
var units []unit_model.Type
units = append(units, unit_model.TypeCode)
if err := repo_service.UpdateRepositoryUnits(db.DefaultContext, repo21, nil, units); err != nil {
assert.Fail(t, "should have been able to delete code repository unit; failed to %v", err)
}
assert.False(t, repo21.UnitEnabled(db.DefaultContext, unit_model.TypeCode))
session := loginUser(t, "user2")
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadOrganization)
req := NewRequestf(t, "GET", "/api/v1/orgs/%s/repos", org3.Name).
AddTokenAuth(token)
resp := MakeRequest(t, req, http.StatusOK)
var apiRepos []*api.Repository
DecodeJSON(t, resp, &apiRepos)
var repoNames []string
for _, r := range apiRepos {
repoNames = append(repoNames, r.Name)
}
assert.Contains(t, repoNames, repo21.Name)
}
func TestAPIGetRepoByIDUnauthorized(t *testing.T) {
defer tests.PrepareTestEnv(t)()
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
session := loginUser(t, user.Name)
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository)
req := NewRequest(t, "GET", "/api/v1/repositories/2").
AddTokenAuth(token)
MakeRequest(t, req, http.StatusNotFound)
}
func TestAPIRepoMigrate(t *testing.T) {
testCases := []struct {
ctxUserID, userID int64
cloneURL, repoName string
expectedStatus int
}{
{ctxUserID: 1, userID: 2, cloneURL: "https://github.com/go-gitea/test_repo.git", repoName: "git-admin", expectedStatus: http.StatusCreated},
{ctxUserID: 2, userID: 2, cloneURL: "https://github.com/go-gitea/test_repo.git", repoName: "git-own", expectedStatus: http.StatusCreated},
{ctxUserID: 2, userID: 1, cloneURL: "https://github.com/go-gitea/test_repo.git", repoName: "git-bad", expectedStatus: http.StatusForbidden},
{ctxUserID: 2, userID: 3, cloneURL: "https://github.com/go-gitea/test_repo.git", repoName: "git-org", expectedStatus: http.StatusCreated},
{ctxUserID: 2, userID: 6, cloneURL: "https://github.com/go-gitea/test_repo.git", repoName: "git-bad-org", expectedStatus: http.StatusForbidden},
{ctxUserID: 2, userID: 3, cloneURL: "https://localhost:3000/user/test_repo.git", repoName: "private-ip", expectedStatus: http.StatusUnprocessableEntity},
{ctxUserID: 2, userID: 3, cloneURL: "https://10.0.0.1/user/test_repo.git", repoName: "private-ip", expectedStatus: http.StatusUnprocessableEntity},
}
defer tests.PrepareTestEnv(t)()
for _, testCase := range testCases {
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: testCase.ctxUserID})
session := loginUser(t, user.Name)
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
req := NewRequestWithJSON(t, "POST", "/api/v1/repos/migrate", &api.MigrateRepoOptions{
CloneAddr: testCase.cloneURL,
RepoOwnerID: testCase.userID,
RepoName: testCase.repoName,
}).AddTokenAuth(token)
resp := MakeRequest(t, req, NoExpectedStatus)
if resp.Code == http.StatusUnprocessableEntity {
respJSON := map[string]string{}
DecodeJSON(t, resp, &respJSON)
switch respJSON["message"] {
case "Remote visit addressed rate limitation.":
t.Log("test hit github rate limitation")
case "You can not import from disallowed hosts.":
assert.EqualValues(t, "private-ip", testCase.repoName)
default:
assert.FailNow(t, "unexpected error '%v' on url '%s'", respJSON["message"], testCase.cloneURL)
}
} else {
assert.EqualValues(t, testCase.expectedStatus, resp.Code)
}
}
}
func TestAPIRepoMigrateConflict(t *testing.T) {
onGiteaRun(t, testAPIRepoMigrateConflict)
}
func testAPIRepoMigrateConflict(t *testing.T, u *url.URL) {
username := "user2"
baseAPITestContext := NewAPITestContext(t, username, "repo1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
u.Path = baseAPITestContext.GitPath()
t.Run("Existing", func(t *testing.T) {
httpContext := baseAPITestContext
httpContext.Reponame = "repo-tmp-17"
t.Run("CreateRepo", doAPICreateRepository(httpContext, false))
user, err := user_model.GetUserByName(db.DefaultContext, httpContext.Username)
assert.NoError(t, err)
userID := user.ID
cloneURL := "https://github.com/go-gitea/test_repo.git"
req := NewRequestWithJSON(t, "POST", "/api/v1/repos/migrate",
&api.MigrateRepoOptions{
CloneAddr: cloneURL,
RepoOwnerID: userID,
RepoName: httpContext.Reponame,
}).
AddTokenAuth(httpContext.Token)
resp := httpContext.Session.MakeRequest(t, req, http.StatusConflict)
respJSON := map[string]string{}
DecodeJSON(t, resp, &respJSON)
assert.Equal(t, "The repository with the same name already exists.", respJSON["message"])
})
}
// mirror-sync must fail with "400 (Bad Request)" when an attempt is made to
// sync a non-mirror repository.
func TestAPIMirrorSyncNonMirrorRepo(t *testing.T) {
defer tests.PrepareTestEnv(t)()
session := loginUser(t, "user2")
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
var repo api.Repository
req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1")
resp := MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &repo)
assert.False(t, repo.Mirror)
req = NewRequestf(t, "POST", "/api/v1/repos/user2/repo1/mirror-sync").
AddTokenAuth(token)
resp = MakeRequest(t, req, http.StatusBadRequest)
errRespJSON := map[string]string{}
DecodeJSON(t, resp, &errRespJSON)
assert.Equal(t, "Repository is not a mirror", errRespJSON["message"])
}
func TestAPIOrgRepoCreate(t *testing.T) {
testCases := []struct {
ctxUserID int64
orgName, repoName string
expectedStatus int
}{
{ctxUserID: 1, orgName: "org3", repoName: "repo-admin", expectedStatus: http.StatusCreated},
{ctxUserID: 2, orgName: "org3", repoName: "repo-own", expectedStatus: http.StatusCreated},
{ctxUserID: 2, orgName: "org6", repoName: "repo-bad-org", expectedStatus: http.StatusForbidden},
{ctxUserID: 28, orgName: "org3", repoName: "repo-creator", expectedStatus: http.StatusCreated},
{ctxUserID: 28, orgName: "org6", repoName: "repo-not-creator", expectedStatus: http.StatusForbidden},
}
defer tests.PrepareTestEnv(t)()
for _, testCase := range testCases {
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: testCase.ctxUserID})
session := loginUser(t, user.Name)
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization, auth_model.AccessTokenScopeWriteRepository)
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/org/%s/repos", testCase.orgName), &api.CreateRepoOption{
Name: testCase.repoName,
}).AddTokenAuth(token)
MakeRequest(t, req, testCase.expectedStatus)
}
}
func TestAPIRepoCreateConflict(t *testing.T) {
onGiteaRun(t, testAPIRepoCreateConflict)
}
func testAPIRepoCreateConflict(t *testing.T, u *url.URL) {
username := "user2"
baseAPITestContext := NewAPITestContext(t, username, "repo1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
u.Path = baseAPITestContext.GitPath()
t.Run("Existing", func(t *testing.T) {
httpContext := baseAPITestContext
httpContext.Reponame = "repo-tmp-17"
t.Run("CreateRepo", doAPICreateRepository(httpContext, false))
req := NewRequestWithJSON(t, "POST", "/api/v1/user/repos",
&api.CreateRepoOption{
Name: httpContext.Reponame,
}).
AddTokenAuth(httpContext.Token)
resp := httpContext.Session.MakeRequest(t, req, http.StatusConflict)
respJSON := map[string]string{}
DecodeJSON(t, resp, &respJSON)
assert.Equal(t, "The repository with the same name already exists.", respJSON["message"])
})
}
func TestAPIRepoTransfer(t *testing.T) {
testCases := []struct {
ctxUserID int64
newOwner string
teams *[]int64
expectedStatus int
}{
// Disclaimer for test story: "user1" is an admin, "user2" is normal user and part of in owner team of org "org3"
// Transfer to a user with teams in another org should fail
{ctxUserID: 1, newOwner: "org3", teams: &[]int64{5}, expectedStatus: http.StatusForbidden},
// Transfer to a user with non-existent team IDs should fail
{ctxUserID: 1, newOwner: "user2", teams: &[]int64{2}, expectedStatus: http.StatusUnprocessableEntity},
// Transfer should go through
{ctxUserID: 1, newOwner: "org3", teams: &[]int64{2}, expectedStatus: http.StatusAccepted},
// Let user transfer it back to himself
{ctxUserID: 2, newOwner: "user2", expectedStatus: http.StatusAccepted},
// And revert transfer
{ctxUserID: 2, newOwner: "org3", teams: &[]int64{2}, expectedStatus: http.StatusAccepted},
// Cannot start transfer to an existing repo
{ctxUserID: 2, newOwner: "org3", teams: nil, expectedStatus: http.StatusUnprocessableEntity},
// Start transfer, repo is now in pending transfer mode
{ctxUserID: 2, newOwner: "org6", teams: nil, expectedStatus: http.StatusCreated},
}
defer tests.PrepareTestEnv(t)()
// create repo to move
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
session := loginUser(t, user.Name)
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
repoName := "moveME"
apiRepo := new(api.Repository)
req := NewRequestWithJSON(t, "POST", "/api/v1/user/repos", &api.CreateRepoOption{
Name: repoName,
Description: "repo move around",
Private: false,
Readme: "Default",
AutoInit: true,
}).AddTokenAuth(token)
resp := MakeRequest(t, req, http.StatusCreated)
DecodeJSON(t, resp, apiRepo)
// start testing
for _, testCase := range testCases {
user = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: testCase.ctxUserID})
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID})
session = loginUser(t, user.Name)
token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer", repo.OwnerName, repo.Name), &api.TransferRepoOption{
NewOwner: testCase.newOwner,
TeamIDs: testCase.teams,
}).AddTokenAuth(token)
MakeRequest(t, req, testCase.expectedStatus)
}
// cleanup
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID})
_ = repo_service.DeleteRepositoryDirectly(db.DefaultContext, user, repo.ID)
}
func transfer(t *testing.T) *repo_model.Repository {
// create repo to move
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
session := loginUser(t, user.Name)
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
repoName := "moveME"
apiRepo := new(api.Repository)
req := NewRequestWithJSON(t, "POST", "/api/v1/user/repos", &api.CreateRepoOption{
Name: repoName,
Description: "repo move around",
Private: false,
Readme: "Default",
AutoInit: true,
}).AddTokenAuth(token)
resp := MakeRequest(t, req, http.StatusCreated)
DecodeJSON(t, resp, apiRepo)
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID})
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer", repo.OwnerName, repo.Name), &api.TransferRepoOption{
NewOwner: "user4",
}).AddTokenAuth(token)
MakeRequest(t, req, http.StatusCreated)
return repo
}
func TestAPIAcceptTransfer(t *testing.T) {
defer tests.PrepareTestEnv(t)()
repo := transfer(t)
// try to accept with not authorized user
session := loginUser(t, "user2")
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer/reject", repo.OwnerName, repo.Name)).
AddTokenAuth(token)
MakeRequest(t, req, http.StatusForbidden)
// try to accept repo that's not marked as transferred
req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer/accept", "user2", "repo1")).
AddTokenAuth(token)
MakeRequest(t, req, http.StatusNotFound)
// accept transfer
session = loginUser(t, "user4")
token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer/accept", repo.OwnerName, repo.Name)).
AddTokenAuth(token)
resp := MakeRequest(t, req, http.StatusAccepted)
apiRepo := new(api.Repository)
DecodeJSON(t, resp, apiRepo)
assert.Equal(t, "user4", apiRepo.Owner.UserName)
}
func TestAPIRejectTransfer(t *testing.T) {
defer tests.PrepareTestEnv(t)()
repo := transfer(t)
// try to reject with not authorized user
session := loginUser(t, "user2")
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer/reject", repo.OwnerName, repo.Name)).
AddTokenAuth(token)
MakeRequest(t, req, http.StatusForbidden)
// try to reject repo that's not marked as transferred
req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer/reject", "user2", "repo1")).
AddTokenAuth(token)
MakeRequest(t, req, http.StatusNotFound)
// reject transfer
session = loginUser(t, "user4")
token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/transfer/reject", repo.OwnerName, repo.Name)).
AddTokenAuth(token)
resp := MakeRequest(t, req, http.StatusOK)
apiRepo := new(api.Repository)
DecodeJSON(t, resp, apiRepo)
assert.Equal(t, "user2", apiRepo.Owner.UserName)
}
func TestAPIGenerateRepo(t *testing.T) {
defer tests.PrepareTestEnv(t)()
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
session := loginUser(t, user.Name)
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
templateRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 44})
// user
repo := new(api.Repository)
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/generate", templateRepo.OwnerName, templateRepo.Name), &api.GenerateRepoOption{
Owner: user.Name,
Name: "new-repo",
Description: "test generate repo",
Private: false,
GitContent: true,
}).AddTokenAuth(token)
resp := MakeRequest(t, req, http.StatusCreated)
DecodeJSON(t, resp, repo)
assert.Equal(t, "new-repo", repo.Name)
// org
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/generate", templateRepo.OwnerName, templateRepo.Name), &api.GenerateRepoOption{
Owner: "org3",
Name: "new-repo",
Description: "test generate repo",
Private: false,
GitContent: true,
}).AddTokenAuth(token)
resp = MakeRequest(t, req, http.StatusCreated)
DecodeJSON(t, resp, repo)
assert.Equal(t, "new-repo", repo.Name)
}
func TestAPIRepoGetReviewers(t *testing.T) {
defer tests.PrepareTestEnv(t)()
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
session := loginUser(t, user.Name)
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository)
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/reviewers", user.Name, repo.Name).
AddTokenAuth(token)
resp := MakeRequest(t, req, http.StatusOK)
var reviewers []*api.User
DecodeJSON(t, resp, &reviewers)
if assert.Len(t, reviewers, 3) {
assert.ElementsMatch(t, []int64{1, 4, 11}, []int64{reviewers[0].ID, reviewers[1].ID, reviewers[2].ID})
}
}
func TestAPIRepoGetAssignees(t *testing.T) {
defer tests.PrepareTestEnv(t)()
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
session := loginUser(t, user.Name)
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository)
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/assignees", user.Name, repo.Name).
AddTokenAuth(token)
resp := MakeRequest(t, req, http.StatusOK)
var assignees []*api.User
DecodeJSON(t, resp, &assignees)
assert.Len(t, assignees, 1)
}