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]>
218 lines
7.9 KiB
Go
218 lines
7.9 KiB
Go
// Copyright 2026 Hanzo AI, Inc. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package project
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/hanzoai/git/models/db"
|
|
issues_model "github.com/hanzoai/git/models/issues"
|
|
org_model "github.com/hanzoai/git/models/organization"
|
|
project_model "github.com/hanzoai/git/models/project"
|
|
repo_model "github.com/hanzoai/git/models/repo"
|
|
"github.com/hanzoai/git/models/unittest"
|
|
user_model "github.com/hanzoai/git/models/user"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func Test_Projects(t *testing.T) {
|
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
|
|
|
userAdmin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
|
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
|
org3 := unittest.AssertExistsAndLoadBean(t, &org_model.Organization{ID: 3})
|
|
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
|
// user15 is on org3's team7 (write access to the public repo32 only), so it can see org3 public repos
|
|
// but has no access to the private repo3 — a genuine "no permission to the private repo" org member.
|
|
user15 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 15})
|
|
|
|
t.Run("User projects", func(t *testing.T) {
|
|
pi1 := project_model.ProjectIssue{
|
|
ProjectID: 4,
|
|
IssueID: 1,
|
|
ProjectColumnID: 4,
|
|
}
|
|
err := db.Insert(t.Context(), &pi1)
|
|
assert.NoError(t, err)
|
|
defer func() {
|
|
_, err = db.DeleteByID[project_model.ProjectIssue](t.Context(), pi1.ID)
|
|
assert.NoError(t, err)
|
|
}()
|
|
|
|
pi2 := project_model.ProjectIssue{
|
|
ProjectID: 4,
|
|
IssueID: 4,
|
|
ProjectColumnID: 4,
|
|
}
|
|
err = db.Insert(t.Context(), &pi2)
|
|
assert.NoError(t, err)
|
|
defer func() {
|
|
_, err = db.DeleteByID[project_model.ProjectIssue](t.Context(), pi2.ID)
|
|
assert.NoError(t, err)
|
|
}()
|
|
|
|
projects, err := db.Find[project_model.Project](t.Context(), project_model.SearchOptions{
|
|
OwnerID: user2.ID,
|
|
})
|
|
assert.NoError(t, err)
|
|
assert.Len(t, projects, 3)
|
|
assert.EqualValues(t, 4, projects[0].ID)
|
|
|
|
t.Run("Authenticated user", func(t *testing.T) {
|
|
columnIssues, err := LoadIssuesFromProject(t.Context(), projects[0], &issues_model.IssuesOptions{
|
|
Owner: user2,
|
|
Doer: user2,
|
|
})
|
|
assert.NoError(t, err)
|
|
assert.Len(t, columnIssues, 1) // 4 has 2 issues, 6 will not contains here because 0 issues
|
|
assert.Len(t, columnIssues[4], 2) // user2 can visit both issues, one from public repository one from private repository
|
|
})
|
|
|
|
t.Run("Anonymous user", func(t *testing.T) {
|
|
columnIssues, err := LoadIssuesFromProject(t.Context(), projects[0], &issues_model.IssuesOptions{
|
|
AllPublic: true,
|
|
})
|
|
assert.NoError(t, err)
|
|
assert.Len(t, columnIssues, 1)
|
|
assert.Len(t, columnIssues[4], 1) // anonymous user can only visit public repo issues
|
|
})
|
|
|
|
t.Run("Authenticated user with no permission to the private repo", func(t *testing.T) {
|
|
columnIssues, err := LoadIssuesFromProject(t.Context(), projects[0], &issues_model.IssuesOptions{
|
|
Owner: user2,
|
|
Doer: user4,
|
|
})
|
|
assert.NoError(t, err)
|
|
assert.Len(t, columnIssues, 1)
|
|
assert.Len(t, columnIssues[4], 1) // user4 can only visit public repo issues
|
|
})
|
|
})
|
|
|
|
t.Run("Org projects", func(t *testing.T) {
|
|
project1 := project_model.Project{
|
|
Title: "project in an org",
|
|
OwnerID: org3.ID,
|
|
Type: project_model.TypeOrganization,
|
|
TemplateType: project_model.TemplateTypeBasicKanban,
|
|
}
|
|
err := project_model.NewProject(t.Context(), &project1)
|
|
assert.NoError(t, err)
|
|
defer func() {
|
|
err := project_model.DeleteProjectByID(t.Context(), project1.ID)
|
|
assert.NoError(t, err)
|
|
}()
|
|
|
|
// Get the default column created by the template (issues will be assigned here)
|
|
defaultColumn, err := project1.MustDefaultColumn(t.Context())
|
|
assert.NoError(t, err)
|
|
|
|
// issue 6 belongs to private repo 3 under org 3
|
|
issue6 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 6})
|
|
err = issues_model.IssueAssignOrRemoveProject(t.Context(), issue6, user2, []int64{project1.ID})
|
|
assert.NoError(t, err)
|
|
|
|
// issue 16 belongs to public repo 16 under org 3
|
|
issue16 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 16})
|
|
err = issues_model.IssueAssignOrRemoveProject(t.Context(), issue16, user2, []int64{project1.ID})
|
|
assert.NoError(t, err)
|
|
|
|
projects, err := db.Find[project_model.Project](t.Context(), project_model.SearchOptions{
|
|
OwnerID: org3.ID,
|
|
})
|
|
assert.NoError(t, err)
|
|
assert.Len(t, projects, 1)
|
|
assert.Equal(t, project1.ID, projects[0].ID)
|
|
|
|
t.Run("Authenticated user", func(t *testing.T) {
|
|
columnIssues, err := LoadIssuesFromProject(t.Context(), projects[0], &issues_model.IssuesOptions{
|
|
Owner: org3.AsUser(),
|
|
Doer: userAdmin,
|
|
})
|
|
assert.NoError(t, err)
|
|
assert.Len(t, columnIssues, 1) // default column has 2 issues
|
|
assert.Len(t, columnIssues[defaultColumn.ID], 2) // admin can visit both issues, one from public repository one from private repository
|
|
})
|
|
|
|
t.Run("Anonymous user", func(t *testing.T) {
|
|
columnIssues, err := LoadIssuesFromProject(t.Context(), projects[0], &issues_model.IssuesOptions{
|
|
AllPublic: true,
|
|
})
|
|
assert.NoError(t, err)
|
|
assert.Len(t, columnIssues, 1)
|
|
assert.Len(t, columnIssues[defaultColumn.ID], 1) // anonymous user can only visit public repo issues
|
|
})
|
|
|
|
t.Run("Authenticated user with no permission to the private repo", func(t *testing.T) {
|
|
// user2 is on org3's Owners team and has owner access to the private repo3, so it is not a
|
|
// valid "no permission" subject; user15 has no access to repo3 but can see the public repo32.
|
|
columnIssues, err := LoadIssuesFromProject(t.Context(), projects[0], &issues_model.IssuesOptions{
|
|
Owner: org3.AsUser(),
|
|
Doer: user15,
|
|
})
|
|
assert.NoError(t, err)
|
|
assert.Len(t, columnIssues, 1)
|
|
assert.Len(t, columnIssues[defaultColumn.ID], 1) // user15 can only visit public repo issues
|
|
})
|
|
|
|
t.Run("Org owner team member", func(t *testing.T) {
|
|
// user2 is on org3's Owners team, so it has access to the private repo3 and must see both the
|
|
// public and the private issue — the owner-team access that team.authorize grants at runtime.
|
|
columnIssues, err := LoadIssuesFromProject(t.Context(), projects[0], &issues_model.IssuesOptions{
|
|
Owner: org3.AsUser(),
|
|
Doer: user2,
|
|
})
|
|
assert.NoError(t, err)
|
|
assert.Len(t, columnIssues, 1)
|
|
assert.Len(t, columnIssues[defaultColumn.ID], 2) // owner-team member visits both public and private issues
|
|
})
|
|
})
|
|
|
|
t.Run("Repository projects", func(t *testing.T) {
|
|
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
|
|
|
projects, err := db.Find[project_model.Project](t.Context(), project_model.SearchOptions{
|
|
RepoID: repo1.ID,
|
|
})
|
|
assert.NoError(t, err)
|
|
assert.Len(t, projects, 1)
|
|
assert.EqualValues(t, 1, projects[0].ID)
|
|
|
|
t.Run("Authenticated user", func(t *testing.T) {
|
|
columnIssues, err := LoadIssuesFromProject(t.Context(), projects[0], &issues_model.IssuesOptions{
|
|
RepoIDs: []int64{repo1.ID},
|
|
Doer: userAdmin,
|
|
})
|
|
assert.NoError(t, err)
|
|
assert.Len(t, columnIssues, 3)
|
|
assert.Len(t, columnIssues[1], 2)
|
|
assert.Len(t, columnIssues[2], 1)
|
|
assert.Len(t, columnIssues[3], 1)
|
|
})
|
|
|
|
t.Run("Anonymous user", func(t *testing.T) {
|
|
columnIssues, err := LoadIssuesFromProject(t.Context(), projects[0], &issues_model.IssuesOptions{
|
|
AllPublic: true,
|
|
})
|
|
assert.NoError(t, err)
|
|
assert.Len(t, columnIssues, 3)
|
|
assert.Len(t, columnIssues[1], 2)
|
|
assert.Len(t, columnIssues[2], 1)
|
|
assert.Len(t, columnIssues[3], 1)
|
|
})
|
|
|
|
t.Run("Authenticated user with no permission to the private repo", func(t *testing.T) {
|
|
columnIssues, err := LoadIssuesFromProject(t.Context(), projects[0], &issues_model.IssuesOptions{
|
|
RepoIDs: []int64{repo1.ID},
|
|
Doer: user2,
|
|
})
|
|
assert.NoError(t, err)
|
|
assert.Len(t, columnIssues, 3)
|
|
assert.Len(t, columnIssues[1], 2)
|
|
assert.Len(t, columnIssues[2], 1)
|
|
assert.Len(t, columnIssues[3], 1)
|
|
})
|
|
})
|
|
}
|