mirror of
https://github.com/go-gitea/gitea.git
synced 2026-11-02 13:44:16 +09:00
Follow-up to https://github.com/go-gitea/gitea/pull/30205, per https://github.com/go-gitea/gitea/pull/30205#discussion_r4435046605. Moving a card on a project board is a project operation, so project write is enough again. Repository boards already show every card to project writers, who then got a 404 when moving one. The per-card archived check is removed too. The frontend sends every card of the target column, so one card from an archived repository rejected all drops and reorders into that column, silently. Project workflows already skip archived repositories, and an archived repository's own board is read-only. --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
93 lines
3.4 KiB
Go
93 lines
3.4 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package project
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"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/unittest"
|
|
user_model "gitea.dev/models/user"
|
|
"gitea.dev/services/contexttest"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestMain(m *testing.M) {
|
|
unittest.MainTest(m)
|
|
}
|
|
|
|
// TestFindColumn covers the scoping rule every board handler depends on: a request only
|
|
// resolves projects owned by the scope its route assigned, so an ID belonging to anyone
|
|
// else reads as not found rather than leaking across owners.
|
|
func TestFindColumn(t *testing.T) {
|
|
unittest.PrepareTestEnv(t)
|
|
for _, tc := range []struct {
|
|
name string
|
|
projectID string
|
|
columnID string
|
|
doerID int64
|
|
repoScoped bool
|
|
resolves bool
|
|
}{
|
|
{"repository project", "1", "2", 2, true, true},
|
|
{"owner project", "4", "4", 2, false, true},
|
|
{"repository board cannot reach an owner project", "4", "1", 2, true, false},
|
|
{"owner board cannot reach another owner's project", "4", "4", 1, false, false},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
ctx, _ := contexttest.MockContext(t, "user2/-/projects")
|
|
contexttest.LoadUser(t, ctx, tc.doerID)
|
|
if tc.repoScoped {
|
|
contexttest.LoadRepo(t, ctx, 1)
|
|
} else {
|
|
ctx.ContextUser = ctx.Doer
|
|
}
|
|
ctx.SetPathParam("id", tc.projectID)
|
|
ctx.SetPathParam("columnID", tc.columnID)
|
|
|
|
project, column := findColumn(ctx)
|
|
assert.Equal(t, tc.resolves, project != nil)
|
|
assert.Equal(t, tc.resolves, column != nil)
|
|
if tc.resolves {
|
|
assert.False(t, ctx.Written())
|
|
} else {
|
|
// a foreign ID must read as not found, never as a 500
|
|
assert.Equal(t, http.StatusNotFound, ctx.Resp.WrittenStatus())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMoveIssuesNeedsOnlyProjectWrite(t *testing.T) {
|
|
unittest.PrepareTestEnv(t)
|
|
require.NoError(t, repo_model.UpdateRepositoryColsNoAutoTime(t.Context(), &repo_model.Repository{ID: 3, IsArchived: true}, "is_archived"))
|
|
ctx, _ := contexttest.MockContext(t, "POST /org3/-/projects")
|
|
contexttest.LoadUser(t, ctx, 15)
|
|
ctx.ContextUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
|
|
project := &project_model.Project{Title: "board", OwnerID: 3, CreatorID: 2, Type: project_model.TypeOrganization}
|
|
require.NoError(t, project_model.NewProject(ctx, project))
|
|
require.NoError(t, project_model.NewColumn(ctx, &project_model.Column{Title: "Backlog", ProjectID: project.ID, Default: true}))
|
|
column := &project_model.Column{Title: "Done", ProjectID: project.ID}
|
|
require.NoError(t, project_model.NewColumn(ctx, column))
|
|
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 6})
|
|
_, _, err := issues_model.IssueAssignOrRemoveProject(ctx, issue, unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}), []int64{project.ID})
|
|
require.NoError(t, err)
|
|
ctx.SetPathParam("id", strconv.FormatInt(project.ID, 10))
|
|
ctx.SetPathParam("columnID", strconv.FormatInt(column.ID, 10))
|
|
ctx.Req.Body = io.NopCloser(strings.NewReader(fmt.Sprintf(`{"issues":[{"issueID":%d,"sorting":0}]}`, issue.ID)))
|
|
|
|
MoveIssues(ctx)
|
|
assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus())
|
|
unittest.AssertExistsAndLoadBean(t, &project_model.ProjectIssue{IssueID: issue.ID, ProjectID: project.ID, ProjectColumnID: column.ID})
|
|
}
|