mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2026-05-12 22:10:25 +00:00
Repository-specific personal access tokens will allow a user's access tokens to be restricted to accessing zero-or-more specific repositories. Currently they can be configured as "All", or "Public only", and this project will add a third configuration option allowing specific repositories. This PR is part of a series (#11311), and builds on the infrastructure work in #11434. In this PR, repository-specific access tokens are implemented on the universal permission checks performed by the API middleware, affecting ~182 API endpoints that perform permission checks based upon repositories referenced in their API path (eg. `/v1/api/repos/{owner}/{repo}/...`). **Breaking change:** API access with a public-only access token would previously return a `403 Forbidden` error when attempting to access a private repository where the repository is on the API path. As part of incorporating the public-only logic into the centralized permission check, these APIs will now return `404 Not Found` instead, consistent with how repository-specific access tokens, and other permissions checks, are implemented in order to reduce the risk of data probing through error messages. For larger context on the usage and future incoming work, the description of #11311 can be referenced. ## Checklist The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org). ### Tests for Go changes (can be removed for JavaScript changes) - I added test coverage for Go changes... - [ ] in their respective `*_test.go` for unit tests. - [x] in the `tests/integration` directory if it involves interactions with a live Forgejo server. - I ran... - [x] `make pr-go` before pushing ### Documentation - [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change. - [x] I did not document these changes and I do not expect someone else to do it. ### Release notes - [x] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change. - As there is no end-user accessibility to create repo-specific access tokens, this functionality will not be accessible to end-users yet. But the breaking change in error APIs for public-only access tokens will be visible to end-users. - [ ] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change. <!--start release-notes-assistant--> ## Release notes <!--URL:https://codeberg.org/forgejo/forgejo--> - Breaking features - [PR](https://codeberg.org/forgejo/forgejo/pulls/11437): <!--number 11437 --><!--line 0 --><!--description aW1wbGVtZW50IHJlcG8tc3BlY2lmaWMgYWNjZXNzIHRva2VucyBicm9hZGx5IGZvciB1bml2ZXJzYWwgQVBJIHBlcm1pc3Npb24gY2hlY2tzLiAgKipCcmVha2luZzoqKiBBUEkgYWNjZXNzIHdpdGggYSBwdWJsaWMtb25seSBhY2Nlc3MgdG9rZW4gd291bGQgcHJldmlvdXNseSByZXR1cm4gYSBgNDAzIEZvcmJpZGRlbmAgZXJyb3Igd2hlbiBhdHRlbXB0aW5nIHRvIGFjY2VzcyBhIHByaXZhdGUgcmVwb3NpdG9yeSB3aGVyZSB0aGUgcmVwb3NpdG9yeSBpcyBvbiB0aGUgQVBJIHBhdGguICBBcyBwYXJ0IG9mIGluY29ycG9yYXRpbmcgdGhlIHB1YmxpYy1vbmx5IGxvZ2ljIGludG8gdGhlIGNlbnRyYWxpemVkIHBlcm1pc3Npb24gY2hlY2ssIHRoZXNlIEFQSXMgd2lsbCBub3cgcmV0dXJuIGA0MDQgTm90IEZvdW5kYCBpbnN0ZWFkLCBjb25zaXN0ZW50IHdpdGggaG93IG1vc3QgcGVybWlzc2lvbiBjaGVja3MgYXJlIGltcGxlbWVudGVkIGluIG9yZGVyIHRvIHJlZHVjZSB0aGUgcmlzayBvZiBkYXRhIHByb2JpbmcgdGhyb3VnaCBlcnJvciBtZXNzYWdlcy4=-->implement repo-specific access tokens broadly for universal API permission checks. **Breaking:** API access with a public-only access token would previously return a `403 Forbidden` error when attempting to access a private repository where the repository is on the API path. As part of incorporating the public-only logic into the centralized permission check, these APIs will now return `404 Not Found` instead, consistent with how most permission checks are implemented in order to reduce the risk of data probing through error messages.<!--description--> <!--end release-notes-assistant--> Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/11437 Reviewed-by: Andreas Ahlenstorf <aahlenst@noreply.codeberg.org> Co-authored-by: Mathieu Fenniak <mathieu@fenniak.net> Co-committed-by: Mathieu Fenniak <mathieu@fenniak.net>
917 lines
34 KiB
Go
917 lines
34 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 "forgejo.org/models/auth"
|
|
"forgejo.org/models/db"
|
|
access_model "forgejo.org/models/perm/access"
|
|
repo_model "forgejo.org/models/repo"
|
|
unit_model "forgejo.org/models/unit"
|
|
"forgejo.org/models/unittest"
|
|
user_model "forgejo.org/models/user"
|
|
"forgejo.org/modules/git"
|
|
"forgejo.org/modules/setting"
|
|
api "forgejo.org/modules/structs"
|
|
repo_service "forgejo.org/services/repository"
|
|
"forgejo.org/tests"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
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.Equal(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(), "access token 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: 38},
|
|
user: {count: 38},
|
|
user2: {count: 38},
|
|
},
|
|
},
|
|
{
|
|
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.HasAccess(db.DefaultContext, userID, r)
|
|
require.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.Equal(t, "repo1", repo.Name)
|
|
assert.Equal(t, 2, repo.Releases)
|
|
assert.Equal(t, 1, repo.OpenIssues)
|
|
assert.Equal(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.Equal(t, "repo10", repo.Name)
|
|
assert.Equal(t, 1, repo.OpenPulls)
|
|
assert.Equal(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.Equal(t, "repo4", repo.Name)
|
|
assert.Equal(t, 1, repo.Stars)
|
|
}
|
|
|
|
// `/repos/{username}/{reponame}` uses repoAssignment() middleware -- this test runs that middleware through all
|
|
// variations of access token resource access.
|
|
func TestAPIViewRepoAccessTokenResources(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
|
|
var repo api.Repository
|
|
|
|
t.Run("all access token", func(t *testing.T) {
|
|
session := loginUser(t, "user2")
|
|
allToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository)
|
|
|
|
t.Run("allowed public repo1", func(t *testing.T) {
|
|
req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1").AddTokenAuth(allToken)
|
|
resp := MakeRequest(t, req, http.StatusOK)
|
|
DecodeJSON(t, resp, &repo)
|
|
assert.False(t, repo.Private)
|
|
})
|
|
t.Run("allowed private repo2", func(t *testing.T) {
|
|
req := NewRequest(t, "GET", "/api/v1/repos/user2/repo2").AddTokenAuth(allToken)
|
|
resp := MakeRequest(t, req, http.StatusOK)
|
|
DecodeJSON(t, resp, &repo)
|
|
assert.True(t, repo.Private)
|
|
})
|
|
// repo16 is a second repo used in fine-grain testing below, so we include it in other tests as a baseline
|
|
t.Run("allowed private repo16", func(t *testing.T) {
|
|
req := NewRequest(t, "GET", "/api/v1/repos/user2/repo16").AddTokenAuth(allToken)
|
|
resp := MakeRequest(t, req, http.StatusOK)
|
|
DecodeJSON(t, resp, &repo)
|
|
assert.True(t, repo.Private)
|
|
})
|
|
})
|
|
|
|
t.Run("public-only access token", func(t *testing.T) {
|
|
session := loginUser(t, "user2")
|
|
publicOnlyToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopePublicOnly, auth_model.AccessTokenScopeReadRepository)
|
|
|
|
t.Run("allowed public repo1", func(t *testing.T) {
|
|
req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1").AddTokenAuth(publicOnlyToken)
|
|
resp := MakeRequest(t, req, http.StatusOK)
|
|
DecodeJSON(t, resp, &repo)
|
|
assert.False(t, repo.Private)
|
|
})
|
|
t.Run("denied private repo2", func(t *testing.T) {
|
|
req := NewRequest(t, "GET", "/api/v1/repos/user2/repo2").AddTokenAuth(publicOnlyToken)
|
|
MakeRequest(t, req, http.StatusNotFound)
|
|
})
|
|
t.Run("denied private repo16", func(t *testing.T) {
|
|
req := NewRequest(t, "GET", "/api/v1/repos/user2/repo16").AddTokenAuth(publicOnlyToken)
|
|
MakeRequest(t, req, http.StatusNotFound)
|
|
})
|
|
})
|
|
|
|
t.Run("specific repo access token", func(t *testing.T) {
|
|
repo2OnlyToken := createFineGrainedRepoAccessToken(t, "user2",
|
|
[]auth_model.AccessTokenScope{auth_model.AccessTokenScopeReadRepository},
|
|
[]int64{2},
|
|
)
|
|
|
|
t.Run("allowed public repo1", func(t *testing.T) {
|
|
req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1").AddTokenAuth(repo2OnlyToken)
|
|
resp := MakeRequest(t, req, http.StatusOK)
|
|
DecodeJSON(t, resp, &repo)
|
|
assert.False(t, repo.Private)
|
|
})
|
|
t.Run("allowed inside fine-grain repo2", func(t *testing.T) {
|
|
req := NewRequest(t, "GET", "/api/v1/repos/user2/repo2").AddTokenAuth(repo2OnlyToken)
|
|
resp := MakeRequest(t, req, http.StatusOK)
|
|
DecodeJSON(t, resp, &repo)
|
|
assert.True(t, repo.Private)
|
|
})
|
|
t.Run("denied private outside fine-grain repo16", func(t *testing.T) {
|
|
req := NewRequest(t, "GET", "/api/v1/repos/user2/repo16").AddTokenAuth(repo2OnlyToken)
|
|
MakeRequest(t, req, http.StatusNotFound)
|
|
})
|
|
})
|
|
}
|
|
|
|
// Validate that private information on the user profile isn't exposed by way of being an owner of a public repository.
|
|
func TestAPIViewRepoOwnerSettings(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.Equal(t, "user2@noreply.example.org", repo.Owner.Email) // unauthed, always private
|
|
assert.Empty(t, repo.Owner.Pronouns) // user2.keep_pronouns_private = true
|
|
|
|
session := loginUser(t, "user2")
|
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository)
|
|
req = NewRequest(t, "GET", "/api/v1/repos/user2/repo1").AddTokenAuth(token)
|
|
resp = MakeRequest(t, req, http.StatusOK)
|
|
DecodeJSON(t, resp, &repo)
|
|
assert.Equal(t, "user2@noreply.example.org", repo.Owner.Email) // user2.keep_email_private = true
|
|
assert.Equal(t, "he/him", repo.Owner.Pronouns) // user2.keep_pronouns_private = true
|
|
|
|
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.Equal(t, "user12@noreply.example.org", repo.Owner.Email) // unauthed, always private
|
|
|
|
req = NewRequest(t, "GET", "/api/v1/repos/user12/repo10").AddTokenAuth(token)
|
|
resp = MakeRequest(t, req, http.StatusOK)
|
|
DecodeJSON(t, resp, &repo)
|
|
assert.Equal(t, "user12@example.com", repo.Owner.Email) // user2.keep_email_private = false
|
|
}
|
|
|
|
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)
|
|
|
|
require.NoError(t, repo_service.UpdateRepositoryUnits(db.DefaultContext, repo21, nil, units))
|
|
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://code.forgejo.org/forgejo/migration-test.git", repoName: "git-admin", expectedStatus: http.StatusCreated},
|
|
{ctxUserID: 2, userID: 2, cloneURL: "https://code.forgejo.org/forgejo/migration-test.git", repoName: "git-own", expectedStatus: http.StatusCreated},
|
|
{ctxUserID: 2, userID: 1, cloneURL: "https://code.forgejo.org/forgejo/migration-test.git", repoName: "git-bad", expectedStatus: http.StatusForbidden},
|
|
{ctxUserID: 2, userID: 3, cloneURL: "https://code.forgejo.org/forgejo/migration-test.git", repoName: "git-org", expectedStatus: http.StatusCreated},
|
|
{ctxUserID: 2, userID: 6, cloneURL: "https://code.forgejo.org/forgejo/migration-test.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,
|
|
Wiki: true,
|
|
}).AddTokenAuth(token)
|
|
resp := MakeRequest(t, req, NoExpectedStatus)
|
|
require.Equalf(t, testCase.expectedStatus, resp.Code, "unexpected status (may be due to throttling): '%v' on url '%s'", resp.Body.String(), testCase.cloneURL)
|
|
}
|
|
}
|
|
|
|
func TestAPIRepoMigrateConflict(t *testing.T) {
|
|
onApplicationRun(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, nil, git.Sha1ObjectFormat)) // FIXME: use forEachObjectFormat
|
|
|
|
user, err := user_model.GetUserByName(db.DefaultContext, httpContext.Username)
|
|
require.NoError(t, err)
|
|
userID := user.ID
|
|
|
|
cloneURL := "https://code.forgejo.org/forgejo/migration-test.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) {
|
|
onApplicationRun(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, nil, git.Sha1ObjectFormat)) // FIXME: use forEachObjectFormat
|
|
|
|
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)
|
|
}
|
|
|
|
func TestAPIViewRepoObjectFormat(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.Equal(t, "sha1", repo.ObjectFormatName)
|
|
}
|
|
|
|
func TestAPIRepoCommitPull(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
|
|
var pr api.PullRequest
|
|
req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/commits/1a8823cd1a9549fde083f992f6b9b87a7ab74fb3/pull")
|
|
resp := MakeRequest(t, req, http.StatusOK)
|
|
|
|
DecodeJSON(t, resp, &pr)
|
|
assert.EqualValues(t, 1, pr.ID)
|
|
|
|
req = NewRequest(t, "GET", "/api/v1/repos/user2/repo1/commits/not-a-commit/pull")
|
|
MakeRequest(t, req, http.StatusNotFound)
|
|
}
|
|
|
|
func TestAPIListOwnRepoSorting(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, auth_model.AccessTokenScopeReadUser)
|
|
|
|
t.Run("No sorting", func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
|
|
MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/repos").AddTokenAuth(token), http.StatusOK)
|
|
})
|
|
|
|
t.Run("ID sorting", func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
|
|
var repos []api.Repository
|
|
resp := MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/repos?limit=2&order_by=id").AddTokenAuth(token), http.StatusOK)
|
|
DecodeJSON(t, resp, &repos)
|
|
|
|
assert.Len(t, repos, 2)
|
|
assert.EqualValues(t, 1, repos[0].ID)
|
|
assert.EqualValues(t, 2, repos[1].ID)
|
|
})
|
|
|
|
t.Run("Name sorting", func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
|
|
var repos []api.Repository
|
|
resp := MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/repos?limit=2&order_by=name").AddTokenAuth(token), http.StatusOK)
|
|
DecodeJSON(t, resp, &repos)
|
|
|
|
assert.Len(t, repos, 2)
|
|
assert.Equal(t, "big_test_private_4", repos[0].Name)
|
|
// Postgres doesn't do ascii sorting.
|
|
if setting.Database.Type.IsPostgreSQL() {
|
|
assert.Equal(t, "commitsonpr", repos[1].Name)
|
|
} else {
|
|
assert.Equal(t, "commits_search_test", repos[1].Name)
|
|
}
|
|
})
|
|
|
|
t.Run("Reverse alphabetic sorting", func(t *testing.T) {
|
|
defer tests.PrintCurrentTest(t)()
|
|
|
|
var repos []api.Repository
|
|
resp := MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/repos?limit=2&order_by=reversealphabetically").AddTokenAuth(token), http.StatusOK)
|
|
DecodeJSON(t, resp, &repos)
|
|
|
|
assert.Len(t, repos, 2)
|
|
assert.Equal(t, "utf8", repos[0].Name)
|
|
assert.Equal(t, "test_workflows", repos[1].Name)
|
|
})
|
|
}
|