Files
hearth/package/gitea/source/tests/integration/api_project_test.go
T
yukkop 335a74010d
runner nix smoke / nix label and flake smoke (push) Failing after 1m12s
feat: kanban tui
2026-09-27 15:42:51 +00:00

178 lines
6.7 KiB
Go

// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package integration
import (
"fmt"
"math"
"net/http"
"testing"
auth_model "gitea.dev/models/auth"
"gitea.dev/models/db"
project_model "gitea.dev/models/project"
repo_model "gitea.dev/models/repo"
"gitea.dev/models/unittest"
api "gitea.dev/modules/structs"
"gitea.dev/tests"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestAPIRepositoryProjects(t *testing.T) {
defer tests.PrepareTestEnv(t)()
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
ownerSession := loginUser(t, repo.OwnerName)
readToken := getTokenForLoggedInUser(t, ownerSession, auth_model.AccessTokenScopeReadIssue)
writeToken := getTokenForLoggedInUser(t, ownerSession, auth_model.AccessTokenScopeWriteIssue)
baseURL := fmt.Sprintf("/api/v1/repos/%s/projects", repo.FullName())
t.Run("List", func(t *testing.T) {
req := NewRequest(t, "GET", baseURL).AddTokenAuth(readToken)
resp := MakeRequest(t, req, http.StatusOK)
projects := DecodeJSON(t, resp, &[]api.Project{})
require.Len(t, *projects, 1)
assert.Equal(t, int64(1), (*projects)[0].ID)
assert.Equal(t, "First project", (*projects)[0].Title)
})
t.Run("Columns", func(t *testing.T) {
req := NewRequest(t, "GET", baseURL+"/1/columns").AddTokenAuth(readToken)
resp := MakeRequest(t, req, http.StatusOK)
columns := DecodeJSON(t, resp, &[]api.ProjectColumn{})
require.Len(t, *columns, 3)
assert.Equal(t, int64(1), (*columns)[0].ID)
assert.Equal(t, "To Do", (*columns)[0].Title)
})
t.Run("ColumnIssues", func(t *testing.T) {
req := NewRequest(t, "GET", baseURL+"/1/columns/2/issues").AddTokenAuth(readToken)
resp := MakeRequest(t, req, http.StatusOK)
issues := DecodeJSON(t, resp, &[]api.Issue{})
require.Len(t, *issues, 1)
assert.Equal(t, int64(3), (*issues)[0].ID)
})
t.Run("ColumnIssuesPaginationAndDefault", func(t *testing.T) {
req := NewRequest(t, "GET", baseURL+"/1/columns/1/issues?limit=1&page=1").AddTokenAuth(readToken)
resp := MakeRequest(t, req, http.StatusOK)
issues := DecodeJSON(t, resp, &[]api.Issue{})
require.Len(t, *issues, 1)
assert.Equal(t, "2", resp.Header().Get("X-Total-Count"))
req = NewRequest(t, "GET", baseURL+"/1/columns/1/issues?limit=1&page=2").AddTokenAuth(readToken)
resp = MakeRequest(t, req, http.StatusOK)
issues = DecodeJSON(t, resp, &[]api.Issue{})
require.Len(t, *issues, 1)
assert.NotEqual(t, (*issues)[0].ID, int64(0))
})
t.Run("CrossRepositoryProject", func(t *testing.T) {
req := NewRequest(t, "GET", baseURL+"/2/columns").AddTokenAuth(readToken)
MakeRequest(t, req, http.StatusNotFound)
})
t.Run("CrossProjectColumn", func(t *testing.T) {
req := NewRequest(t, "GET", baseURL+"/1/columns/5/issues").AddTokenAuth(readToken)
MakeRequest(t, req, http.StatusNotFound)
})
t.Run("RejectsSortingOverflow", func(t *testing.T) {
projectIssue := unittest.AssertExistsAndLoadBean(t, &project_model.ProjectIssue{ProjectID: 1, IssueID: 3})
sorting := int64(math.MaxInt64)
req := NewRequestWithJSON(t, "POST", baseURL+"/1/issues/3/move", api.MoveProjectIssueOption{
ColumnID: 3,
Sorting: &sorting,
}).AddTokenAuth(writeToken)
MakeRequest(t, req, http.StatusUnprocessableEntity)
unchanged := unittest.AssertExistsAndLoadBean(t, &project_model.ProjectIssue{ProjectID: 1, IssueID: 3})
assert.Equal(t, projectIssue.ProjectColumnID, unchanged.ProjectColumnID)
assert.Equal(t, projectIssue.Sorting, unchanged.Sorting)
})
t.Run("ReordersWithinColumn", func(t *testing.T) {
sorting := int64(0)
req := NewRequestWithJSON(t, "POST", baseURL+"/1/issues/3/move", api.MoveProjectIssueOption{
ColumnID: 1,
Sorting: &sorting,
}).AddTokenAuth(writeToken)
MakeRequest(t, req, http.StatusNoContent)
sorting = 2
req = NewRequestWithJSON(t, "POST", baseURL+"/1/issues/3/move", api.MoveProjectIssueOption{
ColumnID: 1,
Sorting: &sorting,
}).AddTokenAuth(writeToken)
MakeRequest(t, req, http.StatusNoContent)
var projectIssues []project_model.ProjectIssue
err := db.GetEngine(t.Context()).
Where("project_id=? AND project_board_id=?", 1, 1).
OrderBy("sorting").Find(&projectIssues)
require.NoError(t, err)
require.Len(t, projectIssues, 2)
assert.Equal(t, int64(3), projectIssues[1].IssueID)
assert.Equal(t, []int64{0, 1}, []int64{projectIssues[0].Sorting, projectIssues[1].Sorting})
})
t.Run("Move", func(t *testing.T) {
req := NewRequestWithJSON(t, "POST", baseURL+"/1/issues/3/move", api.MoveProjectIssueOption{
ColumnID: 3,
}).AddTokenAuth(writeToken)
MakeRequest(t, req, http.StatusNoContent)
projectIssue := unittest.AssertExistsAndLoadBean(t, &project_model.ProjectIssue{ProjectID: 1, IssueID: 3})
assert.Equal(t, int64(3), projectIssue.ProjectColumnID)
})
t.Run("UnassignedIssue", func(t *testing.T) {
req := NewRequestWithJSON(t, "POST", baseURL+"/1/issues/11/move", api.MoveProjectIssueOption{
ColumnID: 3,
}).AddTokenAuth(writeToken)
MakeRequest(t, req, http.StatusUnprocessableEntity)
})
t.Run("Permission", func(t *testing.T) {
outsiderSession := loginUser(t, "user5")
outsiderToken := getTokenForLoggedInUser(t, outsiderSession, auth_model.AccessTokenScopeWriteIssue)
req := NewRequestWithJSON(t, "POST", baseURL+"/1/issues/3/move", api.MoveProjectIssueOption{
ColumnID: 2,
}).AddTokenAuth(outsiderToken)
MakeRequest(t, req, http.StatusForbidden)
})
t.Run("ClosedProject", func(t *testing.T) {
project := unittest.AssertExistsAndLoadBean(t, &project_model.Project{ID: 1})
require.NoError(t, project_model.ChangeProjectStatus(t.Context(), project, true))
defer func() {
require.NoError(t, project_model.ChangeProjectStatus(t.Context(), project, false))
}()
req := NewRequestWithJSON(t, "POST", baseURL+"/1/issues/3/move", api.MoveProjectIssueOption{
ColumnID: 2,
}).AddTokenAuth(writeToken)
MakeRequest(t, req, http.StatusForbidden)
})
t.Run("ArchivedRepository", func(t *testing.T) {
_, err := db.GetEngine(t.Context()).ID(repo.ID).Cols("is_archived").Update(&repo_model.Repository{IsArchived: true})
require.NoError(t, err)
defer func() {
_, err := db.GetEngine(t.Context()).ID(repo.ID).Cols("is_archived").Update(&repo_model.Repository{IsArchived: false})
require.NoError(t, err)
}()
req := NewRequestWithJSON(t, "POST", baseURL+"/1/issues/3/move", api.MoveProjectIssueOption{
ColumnID: 2,
}).AddTokenAuth(writeToken)
MakeRequest(t, req, http.StatusLocked)
})
t.Run("DisabledProjects", func(t *testing.T) {
disabledSession := loginUser(t, "user5")
disabledToken := getTokenForLoggedInUser(t, disabledSession, auth_model.AccessTokenScopeReadIssue)
req := NewRequest(t, "GET", "/api/v1/repos/user5/repo4/projects").AddTokenAuth(disabledToken)
MakeRequest(t, req, http.StatusNotFound)
})
}