初始提交: Gitea 项目代码
This commit is contained in:
@@ -0,0 +1,367 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
issues_model "gitea.dev/models/issues"
|
||||
project_model "gitea.dev/models/project"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
"gitea.dev/models/unit"
|
||||
"gitea.dev/models/unittest"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/tests"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPrivateRepoProject(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
// not logged in user
|
||||
req := NewRequest(t, "GET", "/user31/-/projects")
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
|
||||
sess := loginUser(t, "user1")
|
||||
req = NewRequest(t, "GET", "/user31/-/projects")
|
||||
sess.MakeRequest(t, req, http.StatusOK)
|
||||
}
|
||||
|
||||
func TestMoveRepoProjectColumns(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2})
|
||||
|
||||
projectsUnit := repo2.MustGetUnit(t.Context(), unit.TypeProjects)
|
||||
assert.True(t, projectsUnit.ProjectsConfig().IsProjectsAllowed(repo_model.ProjectsModeRepo))
|
||||
|
||||
project1 := project_model.Project{
|
||||
Title: "new created project",
|
||||
RepoID: repo2.ID,
|
||||
Type: project_model.TypeRepository,
|
||||
TemplateType: project_model.TemplateTypeNone,
|
||||
}
|
||||
err := project_model.NewProject(t.Context(), &project1)
|
||||
assert.NoError(t, err)
|
||||
|
||||
for i := range 3 {
|
||||
err = project_model.NewColumn(t.Context(), &project_model.Column{
|
||||
Title: fmt.Sprintf("column %d", i+1),
|
||||
ProjectID: project1.ID,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
columns, err := project1.GetColumns(t.Context())
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, columns, 3)
|
||||
assert.EqualValues(t, 0, columns[0].Sorting)
|
||||
assert.EqualValues(t, 1, columns[1].Sorting)
|
||||
assert.EqualValues(t, 2, columns[2].Sorting)
|
||||
|
||||
sess := loginUser(t, "user1")
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("/%s/projects/%d", repo2.FullName(), project1.ID))
|
||||
sess.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s/projects/%d/move", repo2.FullName(), project1.ID), map[string]any{
|
||||
"columns": []map[string]any{
|
||||
{"columnID": columns[1].ID, "sorting": 0},
|
||||
{"columnID": columns[2].ID, "sorting": 1},
|
||||
{"columnID": columns[0].ID, "sorting": 2},
|
||||
},
|
||||
})
|
||||
sess.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
columnsAfter, err := project1.GetColumns(t.Context())
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, columnsAfter, 3)
|
||||
assert.Equal(t, columns[1].ID, columnsAfter[0].ID)
|
||||
assert.Equal(t, columns[2].ID, columnsAfter[1].ID)
|
||||
assert.Equal(t, columns[0].ID, columnsAfter[2].ID)
|
||||
|
||||
assert.NoError(t, project_model.DeleteProjectByID(t.Context(), project1.ID))
|
||||
}
|
||||
|
||||
func TestUpdateIssueProject(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
sess := loginUser(t, "user2")
|
||||
|
||||
t.Run("AssignAndRemove", func(t *testing.T) {
|
||||
req := NewRequestWithValues(t, "POST", "/user2/repo1/issues/projects?issue_ids=2", map[string]string{
|
||||
"id": "1",
|
||||
})
|
||||
sess.MakeRequest(t, req, http.StatusOK)
|
||||
unittest.AssertExistsAndLoadBean(t, &project_model.ProjectIssue{IssueID: 2, ProjectID: 1})
|
||||
|
||||
req = NewRequestWithValues(t, "POST", "/user2/repo1/issues/projects?issue_ids=2", map[string]string{
|
||||
"id": "",
|
||||
})
|
||||
sess.MakeRequest(t, req, http.StatusOK)
|
||||
unittest.AssertNotExistsBean(t, &project_model.ProjectIssue{IssueID: 2, ProjectID: 1})
|
||||
})
|
||||
}
|
||||
|
||||
func TestUpdateIssueProjectColumn(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
// fixture: issue 3 is in project 1 of repo user2/repo1, column "In Progress" (id=2)
|
||||
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 3})
|
||||
assert.EqualValues(t, 1, issue.RepoID)
|
||||
|
||||
sess := loginUser(t, "user2")
|
||||
|
||||
t.Run("MoveColumn", func(t *testing.T) {
|
||||
req := NewRequestWithValues(t, "POST", "/user2/repo1/issues/projects/column", map[string]string{
|
||||
"issue_id": "3",
|
||||
"id": "3",
|
||||
})
|
||||
sess.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
pi := unittest.AssertExistsAndLoadBean(t, &project_model.ProjectIssue{IssueID: 3})
|
||||
assert.EqualValues(t, 3, pi.ProjectColumnID)
|
||||
})
|
||||
|
||||
t.Run("InvalidIssueID", func(t *testing.T) {
|
||||
req := NewRequestWithValues(t, "POST", "/user2/repo1/issues/projects/column", map[string]string{
|
||||
"issue_id": "0",
|
||||
"id": "3",
|
||||
})
|
||||
sess.MakeRequest(t, req, http.StatusNotFound)
|
||||
})
|
||||
|
||||
t.Run("WrongRepo", func(t *testing.T) {
|
||||
req := NewRequestWithValues(t, "POST", "/user2/repo1/issues/projects/column", map[string]string{
|
||||
"issue_id": "6",
|
||||
"id": "3",
|
||||
})
|
||||
sess.MakeRequest(t, req, http.StatusNotFound)
|
||||
})
|
||||
|
||||
t.Run("WrongProject", func(t *testing.T) {
|
||||
project2 := project_model.Project{
|
||||
Title: "second project on repo1",
|
||||
RepoID: 1,
|
||||
Type: project_model.TypeRepository,
|
||||
TemplateType: project_model.TemplateTypeNone,
|
||||
}
|
||||
require.NoError(t, project_model.NewProject(t.Context(), &project2))
|
||||
require.NoError(t, project_model.NewColumn(t.Context(), &project_model.Column{
|
||||
Title: "other column",
|
||||
ProjectID: project2.ID,
|
||||
}))
|
||||
columns, err := project2.GetColumns(t.Context())
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, columns)
|
||||
|
||||
req := NewRequestWithValues(t, "POST", "/user2/repo1/issues/projects/column", map[string]string{
|
||||
"issue_id": "1",
|
||||
"id": strconv.FormatInt(columns[0].ID, 10),
|
||||
})
|
||||
sess.MakeRequest(t, req, http.StatusNotFound)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIssueSidebarProjectColumn(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
// fixture: issue 5 (index=4) is in project 1 of repo user2/repo1, column "Done" (id=3)
|
||||
sess := loginUser(t, "user2")
|
||||
|
||||
req := NewRequest(t, "GET", "/user2/repo1/issues/4")
|
||||
resp := sess.MakeRequest(t, req, http.StatusOK)
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
|
||||
cards := htmlDoc.Find(".flex-relaxed-list > .item.sidebar-project-card")
|
||||
assert.Equal(t, 1, cards.Length())
|
||||
|
||||
title := cards.Find("a span.gt-ellipsis")
|
||||
assert.Contains(t, strings.TrimSpace(title.Text()), "First project")
|
||||
|
||||
columnCombo := cards.Find(".issue-sidebar-combo.sidebar-project-column-combo")
|
||||
assert.Equal(t, 1, columnCombo.Length())
|
||||
|
||||
defaultItem := columnCombo.Find(`.menu .item[data-value="1"]`)
|
||||
assert.Equal(t, 1, defaultItem.Length())
|
||||
|
||||
inProgressItem := columnCombo.Find(`.menu .item[data-value="2"]`)
|
||||
assert.Equal(t, 1, inProgressItem.Length())
|
||||
doneItem := columnCombo.Find(`.menu .item[data-value="3"]`)
|
||||
assert.Equal(t, 1, doneItem.Length())
|
||||
|
||||
comboVal, exists := columnCombo.Find("input.combo-value").Attr("value")
|
||||
assert.True(t, exists)
|
||||
assert.Equal(t, "3", comboVal)
|
||||
|
||||
req = NewRequestWithValues(t, "POST", "/user2/repo1/issues/projects?issue_ids=5", map[string]string{"id": ""})
|
||||
sess.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
req = NewRequest(t, "GET", "/user2/repo1/issues/4")
|
||||
resp = sess.MakeRequest(t, req, http.StatusOK)
|
||||
htmlDoc = NewHTMLParser(t, resp.Body)
|
||||
|
||||
cards = htmlDoc.Find(".flex-relaxed-list > .item.sidebar-project-card")
|
||||
assert.Equal(t, 0, cards.Length())
|
||||
}
|
||||
|
||||
// getProjectIssueIDs returns the set of issue IDs rendered as cards on the project board page.
|
||||
func getProjectIssueIDs(t *testing.T, htmlDoc *HTMLDoc) map[int64]struct{} {
|
||||
t.Helper()
|
||||
ids := make(map[int64]struct{})
|
||||
htmlDoc.Find(".issue-card[data-issue]").Each(func(_ int, s *goquery.Selection) {
|
||||
idStr, exists := s.Attr("data-issue")
|
||||
require.True(t, exists)
|
||||
id, err := strconv.ParseInt(idStr, 10, 64)
|
||||
require.NoError(t, err)
|
||||
ids[id] = struct{}{}
|
||||
})
|
||||
return ids
|
||||
}
|
||||
|
||||
func TestRepoProjectFilterByMilestone(t *testing.T) {
|
||||
// Project 1 is on repo 1 (user2/repo1) and has issues:
|
||||
// issue 1 (milestone_id=0), issue 2 (milestone_id=1), issue 3 (milestone_id=3), issue 5 (milestone_id=0)
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
sess := loginUser(t, "user2")
|
||||
|
||||
t.Run("NoFilter", func(t *testing.T) {
|
||||
req := NewRequest(t, "GET", "/user2/repo1/projects/1")
|
||||
resp := sess.MakeRequest(t, req, http.StatusOK)
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
issueIDs := getProjectIssueIDs(t, htmlDoc)
|
||||
// All issues should be visible
|
||||
assert.Contains(t, issueIDs, int64(1))
|
||||
assert.Contains(t, issueIDs, int64(2))
|
||||
assert.Contains(t, issueIDs, int64(3))
|
||||
assert.Contains(t, issueIDs, int64(5))
|
||||
})
|
||||
|
||||
t.Run("FilterByMilestone", func(t *testing.T) {
|
||||
// milestone_id=1 is "milestone1" (open), only issue 2 has it
|
||||
req := NewRequest(t, "GET", "/user2/repo1/projects/1?milestone=1")
|
||||
resp := sess.MakeRequest(t, req, http.StatusOK)
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
issueIDs := getProjectIssueIDs(t, htmlDoc)
|
||||
assert.Contains(t, issueIDs, int64(2))
|
||||
assert.NotContains(t, issueIDs, int64(1))
|
||||
assert.NotContains(t, issueIDs, int64(3))
|
||||
assert.NotContains(t, issueIDs, int64(5))
|
||||
})
|
||||
|
||||
t.Run("FilterByNoMilestone", func(t *testing.T) {
|
||||
// milestone=-1 means "no milestone", issues 1 and 5 have no milestone
|
||||
req := NewRequest(t, "GET", "/user2/repo1/projects/1?milestone=-1")
|
||||
resp := sess.MakeRequest(t, req, http.StatusOK)
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
issueIDs := getProjectIssueIDs(t, htmlDoc)
|
||||
assert.Contains(t, issueIDs, int64(1))
|
||||
assert.Contains(t, issueIDs, int64(5))
|
||||
assert.NotContains(t, issueIDs, int64(2))
|
||||
assert.NotContains(t, issueIDs, int64(3))
|
||||
})
|
||||
|
||||
t.Run("FilterByClosedMilestone", func(t *testing.T) {
|
||||
// milestone_id=3 is "milestone3" (closed), only issue 3 has it
|
||||
req := NewRequest(t, "GET", "/user2/repo1/projects/1?milestone=3")
|
||||
resp := sess.MakeRequest(t, req, http.StatusOK)
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
issueIDs := getProjectIssueIDs(t, htmlDoc)
|
||||
assert.Contains(t, issueIDs, int64(3))
|
||||
assert.NotContains(t, issueIDs, int64(1))
|
||||
assert.NotContains(t, issueIDs, int64(2))
|
||||
assert.NotContains(t, issueIDs, int64(5))
|
||||
})
|
||||
}
|
||||
|
||||
func TestOrgProjectFilterByMilestone(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
// org3 owns repo32 (public) which has issues 16 and 17
|
||||
org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 32})
|
||||
issue16 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 16})
|
||||
issue17 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 17})
|
||||
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
|
||||
// Create a milestone on repo32 and assign it to issue16
|
||||
milestone := &issues_model.Milestone{
|
||||
RepoID: repo.ID,
|
||||
Name: "org-test-milestone",
|
||||
}
|
||||
require.NoError(t, issues_model.NewMilestone(t.Context(), milestone))
|
||||
|
||||
issue16.MilestoneID = milestone.ID
|
||||
require.NoError(t, issues_model.UpdateIssueCols(t.Context(), issue16, "milestone_id"))
|
||||
|
||||
// Create an org-level project
|
||||
project := project_model.Project{
|
||||
Title: "org milestone filter test",
|
||||
OwnerID: org.ID,
|
||||
Type: project_model.TypeOrganization,
|
||||
TemplateType: project_model.TemplateTypeBasicKanban,
|
||||
}
|
||||
require.NoError(t, project_model.NewProject(t.Context(), &project))
|
||||
|
||||
// Add issues to the project
|
||||
require.NoError(t, issues_model.IssueAssignOrRemoveProject(t.Context(), issue16, user1, []int64{project.ID}))
|
||||
require.NoError(t, issues_model.IssueAssignOrRemoveProject(t.Context(), issue17, user1, []int64{project.ID}))
|
||||
|
||||
sess := loginUser(t, "user1")
|
||||
projectURL := fmt.Sprintf("/org3/-/projects/%d", project.ID)
|
||||
|
||||
t.Run("NoFilter", func(t *testing.T) {
|
||||
req := NewRequest(t, "GET", projectURL)
|
||||
resp := sess.MakeRequest(t, req, http.StatusOK)
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
issueIDs := getProjectIssueIDs(t, htmlDoc)
|
||||
assert.Contains(t, issueIDs, issue16.ID)
|
||||
assert.Contains(t, issueIDs, issue17.ID)
|
||||
})
|
||||
|
||||
t.Run("FilterByMilestone", func(t *testing.T) {
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("%s?milestone=%d", projectURL, milestone.ID))
|
||||
resp := sess.MakeRequest(t, req, http.StatusOK)
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
issueIDs := getProjectIssueIDs(t, htmlDoc)
|
||||
assert.Contains(t, issueIDs, issue16.ID)
|
||||
assert.NotContains(t, issueIDs, issue17.ID)
|
||||
})
|
||||
|
||||
t.Run("FilterByNoMilestone", func(t *testing.T) {
|
||||
req := NewRequest(t, "GET", projectURL+"?milestone=-1")
|
||||
resp := sess.MakeRequest(t, req, http.StatusOK)
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
issueIDs := getProjectIssueIDs(t, htmlDoc)
|
||||
assert.Contains(t, issueIDs, issue17.ID)
|
||||
assert.NotContains(t, issueIDs, issue16.ID)
|
||||
})
|
||||
|
||||
t.Run("AnonymousAccess", func(t *testing.T) {
|
||||
// Anonymous users should be able to view org project boards for public orgs
|
||||
// and the milestone filter should work without exposing private repo data
|
||||
req := NewRequest(t, "GET", projectURL)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
issueIDs := getProjectIssueIDs(t, htmlDoc)
|
||||
// repo32 is public, so anonymous users should see its issues
|
||||
assert.Contains(t, issueIDs, issue16.ID)
|
||||
assert.Contains(t, issueIDs, issue17.ID)
|
||||
|
||||
// Milestone filtering should also work for anonymous users
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("%s?milestone=%d", projectURL, milestone.ID))
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
htmlDoc = NewHTMLParser(t, resp.Body)
|
||||
issueIDs = getProjectIssueIDs(t, htmlDoc)
|
||||
assert.Contains(t, issueIDs, issue16.ID)
|
||||
assert.NotContains(t, issueIDs, issue17.ID)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user