mirror of
https://github.com/hanzoai/forge.git
synced 2026-10-30 15:00:39 +00:00
Source files keep their SPDX-License-Identifier and Hanzo copyright line; the Gitea, Gogs and LFS Test Server copyrights they used to repeat now live once in NOTICE, alongside LICENSE. The header lint accepts any copyright line above the SPDX line, so it no longer demands an upstream one. Co-authored-by: Hanzo Dev <[email protected]>
331 lines
12 KiB
Go
331 lines
12 KiB
Go
// Copyright 2026 Hanzo AI, Inc. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package org
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
|
|
issues_model "github.com/hanzoai/git/models/issues"
|
|
"github.com/hanzoai/git/models/organization"
|
|
"github.com/hanzoai/git/models/perm"
|
|
access_model "github.com/hanzoai/git/models/perm/access"
|
|
repo_model "github.com/hanzoai/git/models/repo"
|
|
"github.com/hanzoai/git/models/unittest"
|
|
user_model "github.com/hanzoai/git/models/user"
|
|
"github.com/hanzoai/git/modules/structs"
|
|
repo_service "github.com/hanzoai/git/services/repository"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestTeam_AddMember(t *testing.T) {
|
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
|
|
|
test := func(team *organization.Team, user *user_model.User) {
|
|
assert.NoError(t, AddTeamMember(t.Context(), team, user))
|
|
unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{UID: user.ID, TeamID: team.ID})
|
|
unittest.CheckConsistencyFor(t, &organization.Team{ID: team.ID}, &user_model.User{ID: team.OrgID})
|
|
}
|
|
|
|
team1 := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 1})
|
|
team3 := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 3})
|
|
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
|
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
|
|
|
test(team1, user2)
|
|
test(team1, user4)
|
|
test(team3, user2)
|
|
}
|
|
|
|
func TestTeam_RemoveMember(t *testing.T) {
|
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
|
|
|
testSuccess := func(team *organization.Team, user *user_model.User) {
|
|
assert.NoError(t, RemoveTeamMember(t.Context(), team, user))
|
|
unittest.AssertNotExistsBean(t, &organization.TeamUser{UID: user.ID, TeamID: team.ID})
|
|
unittest.CheckConsistencyFor(t, &organization.Team{ID: team.ID})
|
|
}
|
|
|
|
team1 := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 1})
|
|
team2 := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2})
|
|
team3 := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 3})
|
|
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
|
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
|
|
|
testSuccess(team1, user4)
|
|
testSuccess(team2, user2)
|
|
testSuccess(team3, user2)
|
|
|
|
err := RemoveTeamMember(t.Context(), team1, user2)
|
|
assert.True(t, organization.IsErrLastOrgOwner(err))
|
|
}
|
|
|
|
func TestRemoveTeamMemberRemovesSubscriptionsAndStopwatches(t *testing.T) {
|
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
|
|
|
ctx := t.Context()
|
|
team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2})
|
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})
|
|
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: repo.ID})
|
|
|
|
assert.NoError(t, repo_model.WatchRepo(ctx, user, repo, true))
|
|
assert.NoError(t, issues_model.CreateOrUpdateIssueWatch(ctx, user.ID, issue.ID, true))
|
|
ok, err := issues_model.CreateIssueStopwatch(ctx, user, issue)
|
|
assert.NoError(t, err)
|
|
assert.True(t, ok)
|
|
|
|
assert.NoError(t, RemoveTeamMember(ctx, team, user))
|
|
|
|
watch, err := repo_model.GetWatch(ctx, user.ID, repo.ID)
|
|
assert.NoError(t, err)
|
|
assert.False(t, repo_model.IsWatchMode(watch.Mode))
|
|
|
|
_, exists, err := issues_model.GetIssueWatch(ctx, user.ID, issue.ID)
|
|
assert.NoError(t, err)
|
|
assert.False(t, exists)
|
|
|
|
hasStopwatch, _, _, err := issues_model.HasUserStopwatch(ctx, user.ID)
|
|
assert.NoError(t, err)
|
|
assert.False(t, hasStopwatch)
|
|
}
|
|
|
|
func TestNewTeam(t *testing.T) {
|
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
|
|
|
const teamName = "newTeamName"
|
|
team := &organization.Team{Name: teamName, OrgID: 3}
|
|
assert.NoError(t, NewTeam(t.Context(), team))
|
|
unittest.AssertExistsAndLoadBean(t, &organization.Team{Name: teamName})
|
|
unittest.CheckConsistencyFor(t, &organization.Team{}, &user_model.User{ID: team.OrgID})
|
|
}
|
|
|
|
func TestUpdateTeam(t *testing.T) {
|
|
// successful update
|
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
|
|
|
team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2})
|
|
team.LowerName = "newname"
|
|
team.Name = "newName"
|
|
team.Description = strings.Repeat("A long description!", 100)
|
|
team.AccessMode = perm.AccessModeAdmin
|
|
assert.NoError(t, UpdateTeam(t.Context(), team, true, false))
|
|
|
|
team = unittest.AssertExistsAndLoadBean(t, &organization.Team{Name: "newName"})
|
|
assert.True(t, strings.HasPrefix(team.Description, "A long description!"))
|
|
|
|
access := unittest.AssertExistsAndLoadBean(t, &access_model.Access{UserID: 4, RepoID: 3})
|
|
assert.Equal(t, perm.AccessModeAdmin, access.Mode)
|
|
|
|
unittest.CheckConsistencyFor(t, &organization.Team{ID: team.ID})
|
|
}
|
|
|
|
func TestUpdateTeam2(t *testing.T) {
|
|
// update to already-existing team
|
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
|
|
|
team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2})
|
|
team.LowerName = "owners"
|
|
team.Name = "Owners"
|
|
team.Description = strings.Repeat("A long description!", 100)
|
|
err := UpdateTeam(t.Context(), team, true, false)
|
|
assert.True(t, organization.IsErrTeamAlreadyExist(err))
|
|
|
|
unittest.CheckConsistencyFor(t, &organization.Team{ID: team.ID})
|
|
}
|
|
|
|
func TestDeleteTeam(t *testing.T) {
|
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
|
|
|
team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2})
|
|
assert.NoError(t, DeleteTeam(t.Context(), team))
|
|
unittest.AssertNotExistsBean(t, &organization.Team{ID: team.ID})
|
|
unittest.AssertNotExistsBean(t, &organization.TeamRepo{TeamID: team.ID})
|
|
unittest.AssertNotExistsBean(t, &organization.TeamUser{TeamID: team.ID})
|
|
|
|
// check that team members don't have "leftover" access to repos
|
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})
|
|
accessMode, err := access_model.AccessLevel(t.Context(), user, repo)
|
|
assert.NoError(t, err)
|
|
assert.Less(t, accessMode, perm.AccessModeWrite)
|
|
}
|
|
|
|
func TestAddTeamMember(t *testing.T) {
|
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
|
|
|
test := func(team *organization.Team, user *user_model.User) {
|
|
assert.NoError(t, AddTeamMember(t.Context(), team, user))
|
|
unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{UID: user.ID, TeamID: team.ID})
|
|
unittest.CheckConsistencyFor(t, &organization.Team{ID: team.ID}, &user_model.User{ID: team.OrgID})
|
|
}
|
|
|
|
team1 := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 1})
|
|
team3 := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 3})
|
|
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
|
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
|
|
|
test(team1, user2)
|
|
test(team1, user4)
|
|
test(team3, user2)
|
|
}
|
|
|
|
func TestRemoveTeamMember(t *testing.T) {
|
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
|
|
|
testSuccess := func(team *organization.Team, user *user_model.User) {
|
|
assert.NoError(t, RemoveTeamMember(t.Context(), team, user))
|
|
unittest.AssertNotExistsBean(t, &organization.TeamUser{UID: user.ID, TeamID: team.ID})
|
|
unittest.CheckConsistencyFor(t, &organization.Team{ID: team.ID})
|
|
}
|
|
|
|
team1 := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 1})
|
|
team2 := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2})
|
|
team3 := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 3})
|
|
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
|
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
|
|
|
testSuccess(team1, user4)
|
|
testSuccess(team2, user2)
|
|
testSuccess(team3, user2)
|
|
|
|
err := RemoveTeamMember(t.Context(), team1, user2)
|
|
assert.True(t, organization.IsErrLastOrgOwner(err))
|
|
}
|
|
|
|
func TestIncludesAllRepositoriesTeams(t *testing.T) {
|
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
|
|
|
testTeamRepositories := func(teamID int64, repoIDs []int64) {
|
|
team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID})
|
|
repos, err := repo_model.GetTeamRepositories(t.Context(), &repo_model.SearchTeamRepoOptions{
|
|
TeamID: team.ID,
|
|
})
|
|
assert.NoError(t, err, "%s: GetTeamRepositories", team.Name)
|
|
assert.Len(t, repos, team.NumRepos, "%s: len repo", team.Name)
|
|
assert.Len(t, repos, len(repoIDs), "%s: repo count", team.Name)
|
|
for i, rid := range repoIDs {
|
|
if rid > 0 {
|
|
assert.True(t, repo_service.HasRepository(t.Context(), team, rid), "%s: HasRepository(%d) %d", rid, i)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get an admin user.
|
|
user, err := user_model.GetUserByID(t.Context(), 1)
|
|
assert.NoError(t, err, "GetUserByID")
|
|
|
|
// Create org.
|
|
org := &organization.Organization{
|
|
Name: "All_repo",
|
|
IsActive: true,
|
|
Type: user_model.UserTypeOrganization,
|
|
Visibility: structs.VisibleTypePublic,
|
|
}
|
|
assert.NoError(t, organization.CreateOrganization(t.Context(), org, user), "CreateOrganization")
|
|
|
|
// Check Owner team.
|
|
ownerTeam, err := org.GetOwnerTeam(t.Context())
|
|
assert.NoError(t, err, "GetOwnerTeam")
|
|
assert.True(t, ownerTeam.IncludesAllRepositories, "Owner team includes all repositories")
|
|
|
|
// Create repos.
|
|
repoIDs := make([]int64, 0)
|
|
for i := range 3 {
|
|
r, err := repo_service.CreateRepositoryDirectly(t.Context(), user, org.AsUser(),
|
|
repo_service.CreateRepoOptions{Name: fmt.Sprintf("repo-%d", i)}, true)
|
|
assert.NoError(t, err, "CreateRepository %d", i)
|
|
if r != nil {
|
|
repoIDs = append(repoIDs, r.ID)
|
|
}
|
|
}
|
|
// Get fresh copy of Owner team after creating repos.
|
|
ownerTeam, err = org.GetOwnerTeam(t.Context())
|
|
assert.NoError(t, err, "GetOwnerTeam")
|
|
|
|
// Create teams and check repositories.
|
|
teams := []*organization.Team{
|
|
ownerTeam,
|
|
{
|
|
OrgID: org.ID,
|
|
Name: "team one",
|
|
AccessMode: perm.AccessModeRead,
|
|
IncludesAllRepositories: true,
|
|
},
|
|
{
|
|
OrgID: org.ID,
|
|
Name: "team 2",
|
|
AccessMode: perm.AccessModeRead,
|
|
IncludesAllRepositories: false,
|
|
},
|
|
{
|
|
OrgID: org.ID,
|
|
Name: "team three",
|
|
AccessMode: perm.AccessModeWrite,
|
|
IncludesAllRepositories: true,
|
|
},
|
|
{
|
|
OrgID: org.ID,
|
|
Name: "team 4",
|
|
AccessMode: perm.AccessModeWrite,
|
|
IncludesAllRepositories: false,
|
|
},
|
|
}
|
|
teamRepos := [][]int64{
|
|
repoIDs,
|
|
repoIDs,
|
|
{},
|
|
repoIDs,
|
|
{},
|
|
}
|
|
for i, team := range teams {
|
|
if i > 0 { // first team is Owner.
|
|
assert.NoError(t, NewTeam(t.Context(), team), "%s: NewTeam", team.Name)
|
|
}
|
|
testTeamRepositories(team.ID, teamRepos[i])
|
|
}
|
|
|
|
// Update teams and check repositories.
|
|
teams[3].IncludesAllRepositories = false
|
|
teams[4].IncludesAllRepositories = true
|
|
teamRepos[4] = repoIDs
|
|
for i, team := range teams {
|
|
assert.NoError(t, UpdateTeam(t.Context(), team, false, true), "%s: UpdateTeam", team.Name)
|
|
testTeamRepositories(team.ID, teamRepos[i])
|
|
}
|
|
|
|
// Create repo and check teams repositories.
|
|
r, err := repo_service.CreateRepositoryDirectly(t.Context(), user, org.AsUser(), repo_service.CreateRepoOptions{Name: "repo-last"}, true)
|
|
assert.NoError(t, err, "CreateRepository last")
|
|
if r != nil {
|
|
repoIDs = append(repoIDs, r.ID)
|
|
}
|
|
teamRepos[0] = repoIDs
|
|
teamRepos[1] = repoIDs
|
|
teamRepos[4] = repoIDs
|
|
for i, team := range teams {
|
|
testTeamRepositories(team.ID, teamRepos[i])
|
|
}
|
|
|
|
// Remove repo and check teams repositories.
|
|
assert.NoError(t, repo_service.DeleteRepositoryDirectly(t.Context(), repoIDs[0]), "DeleteRepository")
|
|
teamRepos[0] = repoIDs[1:]
|
|
teamRepos[1] = repoIDs[1:]
|
|
teamRepos[3] = repoIDs[1:3]
|
|
teamRepos[4] = repoIDs[1:]
|
|
for i, team := range teams {
|
|
testTeamRepositories(team.ID, teamRepos[i])
|
|
}
|
|
|
|
// Wipe created items.
|
|
for i, rid := range repoIDs {
|
|
if i > 0 { // first repo already deleted.
|
|
assert.NoError(t, repo_service.DeleteRepositoryDirectly(t.Context(), rid), "DeleteRepository %d", i)
|
|
}
|
|
}
|
|
assert.NoError(t, DeleteOrganization(t.Context(), org, false), "DeleteOrganization")
|
|
}
|