Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Correctly set the organization num repos #11339

Merged
merged 9 commits into from
May 11, 2020
Merged
Prev Previous commit
Next Next commit
Add test and fix 0 counted orgs
Signed-off-by: Andrew Thornton <[email protected]>
  • Loading branch information
zeripath committed May 11, 2020
commit db46f1756dd17fb60a20d10ee07ffae5552d621b
83 changes: 83 additions & 0 deletions integrations/api_helper_for_declarative_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,3 +266,86 @@ func doAPICreateFile(ctx APITestContext, treepath string, options *api.CreateFil
}
}
}

func doAPICreateOrganization(ctx APITestContext, options *api.CreateOrgOption, callback ...func(*testing.T, api.Organization)) func(t *testing.T) {
return func(t *testing.T) {
url := fmt.Sprintf("/api/v1/orgs?token=%s", ctx.Token)

req := NewRequestWithJSON(t, "POST", url, &options)
if ctx.ExpectedCode != 0 {
ctx.Session.MakeRequest(t, req, ctx.ExpectedCode)
return
}
resp := ctx.Session.MakeRequest(t, req, http.StatusCreated)

var contents api.Organization
DecodeJSON(t, resp, &contents)
if len(callback) > 0 {
callback[0](t, contents)
}
}
}

func doAPICreateOrganizationRepository(ctx APITestContext, orgName string, options *api.CreateRepoOption, callback ...func(*testing.T, api.Repository)) func(t *testing.T) {
return func(t *testing.T) {
url := fmt.Sprintf("/api/v1/orgs/%s/repos?token=%s", orgName, ctx.Token)

req := NewRequestWithJSON(t, "POST", url, &options)
if ctx.ExpectedCode != 0 {
ctx.Session.MakeRequest(t, req, ctx.ExpectedCode)
return
}
resp := ctx.Session.MakeRequest(t, req, http.StatusCreated)

var contents api.Repository
DecodeJSON(t, resp, &contents)
if len(callback) > 0 {
callback[0](t, contents)
}
}
}

func doAPICreateOrganizationTeam(ctx APITestContext, orgName string, options *api.CreateTeamOption, callback ...func(*testing.T, api.Team)) func(t *testing.T) {
return func(t *testing.T) {
url := fmt.Sprintf("/api/v1/orgs/%s/teams?token=%s", orgName, ctx.Token)

req := NewRequestWithJSON(t, "POST", url, &options)
if ctx.ExpectedCode != 0 {
ctx.Session.MakeRequest(t, req, ctx.ExpectedCode)
return
}
resp := ctx.Session.MakeRequest(t, req, http.StatusCreated)

var contents api.Team
DecodeJSON(t, resp, &contents)
if len(callback) > 0 {
callback[0](t, contents)
}
}
}

func doAPIAddUserToOrganizationTeam(ctx APITestContext, teamID int64, username string) func(t *testing.T) {
return func(t *testing.T) {
url := fmt.Sprintf("/api/v1/teams/%d/members/%s?token=%s", teamID, username, ctx.Token)

req := NewRequest(t, "PUT", url)
if ctx.ExpectedCode != 0 {
ctx.Session.MakeRequest(t, req, ctx.ExpectedCode)
return
}
ctx.Session.MakeRequest(t, req, http.StatusNoContent)
}
}

func doAPIAddRepoToOrganizationTeam(ctx APITestContext, teamID int64, orgName, repoName string) func(t *testing.T) {
return func(t *testing.T) {
url := fmt.Sprintf("/api/v1/teams/%d/repos/%s/%s?token=%s", teamID, orgName, repoName, ctx.Token)

req := NewRequest(t, "PUT", url)
if ctx.ExpectedCode != 0 {
ctx.Session.MakeRequest(t, req, ctx.ExpectedCode)
return
}
ctx.Session.MakeRequest(t, req, http.StatusNoContent)
}
}
140 changes: 140 additions & 0 deletions integrations/org_count_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package integrations

import (
"net/url"
"strings"
"testing"

"code.gitea.io/gitea/models"
api "code.gitea.io/gitea/modules/structs"
"github.com/stretchr/testify/assert"
)

func TestOrgCounts(t *testing.T) {
onGiteaRun(t, testOrgCounts)
}

func testOrgCounts(t *testing.T, u *url.URL) {
orgOwner := "user2"
orgName := "testOrg"
orgCollaborator := "user4"
ctx := NewAPITestContext(t, orgOwner, "repo1")

var ownerCountRepos map[string]int
var collabCountRepos map[string]int

t.Run("GetTheOwnersNumRepos", doCheckOrgCounts(orgOwner, map[string]int{},
false,
func(_ *testing.T, calcOrgCounts map[string]int) {
ownerCountRepos = calcOrgCounts
},
))
t.Run("GetTheCollaboratorsNumRepos", doCheckOrgCounts(orgCollaborator, map[string]int{},
false,
func(_ *testing.T, calcOrgCounts map[string]int) {
collabCountRepos = calcOrgCounts
},
))

t.Run("CreatePublicTestOrganization", doAPICreateOrganization(ctx, &api.CreateOrgOption{
UserName: orgName,
Visibility: "public",
}))

// Following the creation of the organization, the orgName must appear in the counts with 0 repos
ownerCountRepos[orgName] = 0

t.Run("AssertNumRepos0ForTestOrg", doCheckOrgCounts(orgOwner, ownerCountRepos, true))

// the collaborator is not a collaborator yet
t.Run("AssertNoTestOrgReposForCollaborator", doCheckOrgCounts(orgCollaborator, collabCountRepos, true))

t.Run("CreateOrganizationPrivateRepo", doAPICreateOrganizationRepository(ctx, orgName, &api.CreateRepoOption{
Name: "privateTestRepo",
AutoInit: true,
Private: true,
}))

ownerCountRepos[orgName] = 1
t.Run("AssertNumRepos1ForTestOrg", doCheckOrgCounts(orgOwner, ownerCountRepos, true))

t.Run("AssertNoTestOrgReposForCollaborator", doCheckOrgCounts(orgCollaborator, collabCountRepos, true))

var testTeam api.Team

t.Run("CreateTeamForPublicTestOrganization", doAPICreateOrganizationTeam(ctx, orgName, &api.CreateTeamOption{
Name: "test",
Permission: "read",
Units: []string{"repo.code", "repo.issues", "repo.wiki", "repo.pulls", "repo.releases"},
CanCreateOrgRepo: true,
}, func(_ *testing.T, team api.Team) {
testTeam = team
}))

t.Run("AssertNoTestOrgReposForCollaborator", doCheckOrgCounts(orgCollaborator, collabCountRepos, true))

t.Run("AddCollboratorToTeam", doAPIAddUserToOrganizationTeam(ctx, testTeam.ID, orgCollaborator))

collabCountRepos[orgName] = 0
t.Run("AssertNumRepos0ForTestOrgForCollaborator", doCheckOrgCounts(orgOwner, ownerCountRepos, true))

// Now create a Public Repo
t.Run("CreateOrganizationPublicRepo", doAPICreateOrganizationRepository(ctx, orgName, &api.CreateRepoOption{
Name: "publicTestRepo",
AutoInit: true,
}))

ownerCountRepos[orgName] = 2
t.Run("AssertNumRepos2ForTestOrg", doCheckOrgCounts(orgOwner, ownerCountRepos, true))
collabCountRepos[orgName] = 1
t.Run("AssertNumRepos1ForTestOrgForCollaborator", doCheckOrgCounts(orgOwner, ownerCountRepos, true))

// Now add the testTeam to the privateRepo
t.Run("AddTestTeamToPrivateRepo", doAPIAddRepoToOrganizationTeam(ctx, testTeam.ID, orgName, "privateTestRepo"))

t.Run("AssertNumRepos2ForTestOrg", doCheckOrgCounts(orgOwner, ownerCountRepos, true))
collabCountRepos[orgName] = 2
t.Run("AssertNumRepos2ForTestOrgForCollaborator", doCheckOrgCounts(orgOwner, ownerCountRepos, true))
}

func doCheckOrgCounts(username string, orgCounts map[string]int, strict bool, callback ...func(*testing.T, map[string]int)) func(t *testing.T) {
canonicalCounts := make(map[string]int, len(orgCounts))

for key, value := range orgCounts {
newKey := strings.TrimSpace(strings.ToLower(key))
canonicalCounts[newKey] = value
}

return func(t *testing.T) {
user := models.AssertExistsAndLoadBean(t, &models.User{
Name: username,
}).(*models.User)

user.GetOrganizations(&models.SearchOrganizationsOptions{All: true})

calcOrgCounts := map[string]int{}

for _, org := range user.Orgs {
calcOrgCounts[org.LowerName] = org.NumRepos
count, ok := canonicalCounts[org.LowerName]
if ok {
assert.True(t, count == org.NumRepos, "Number of Repos in %s is %d when we expected %d", org.Name, org.NumRepos, count)
} else {
assert.False(t, strict, "Did not expect to see %s with count %d", org.Name, org.NumRepos)
}
}

for key, value := range orgCounts {
_, seen := calcOrgCounts[strings.TrimSpace(strings.ToLower(key))]
assert.True(t, seen, "Expected to see %s with %d but did not", key, value)
}

if len(callback) > 0 {
callback[0](t, calcOrgCounts)
}
}
}
8 changes: 5 additions & 3 deletions models/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -726,11 +726,13 @@ func (u *User) GetOrganizations(opts *SearchOrganizationsOptions) error {
groupByStr := groupByCols.String()
groupByStr = groupByStr[0 : len(groupByStr)-1]

sess.Select("`user`.*, count(`repository`.id) as org_count").
sess.Select("`user`.*, count(repo_id) as org_count").
Table("user").
Join("INNER", "org_user", "`org_user`.org_id=`user`.id").
Join("INNER", "repository", "`repository`.owner_id = `org_user`.org_id").
Where(accessibleRepositoryCondition(u)).
Join("LEFT", builder.
Select("id as repo_id, owner_id as repo_owner_id").
From("repository").
Where(accessibleRepositoryCondition(u)), "`repository`.repo_owner_id = `org_user`.org_id").
And("`org_user`.uid=?", u.ID).
GroupBy(groupByStr)
if opts.PageSize != 0 {
Expand Down
2 changes: 1 addition & 1 deletion routers/api/v1/repo/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ func CreateOrgRepo(ctx *context.APIContext, opt api.CreateRepoOption) {
// "403":
// "$ref": "#/responses/forbidden"

org, err := models.GetOrgByName(ctx.Params(":org"))
org, err := models.GetOrgByName(ctx.Params(":orgname"))
if err != nil {
if models.IsErrOrgNotExist(err) {
ctx.Error(http.StatusUnprocessableEntity, "", err)
Expand Down