mirror of https://github.com/go-gitea/gitea.git
596 lines
21 KiB
Go
596 lines
21 KiB
Go
// Copyright 2025 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package integration
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"testing"
|
|
|
|
auth_model "code.gitea.io/gitea/models/auth"
|
|
issues_model "code.gitea.io/gitea/models/issues"
|
|
project_model "code.gitea.io/gitea/models/project"
|
|
repo_model "code.gitea.io/gitea/models/repo"
|
|
"code.gitea.io/gitea/models/unittest"
|
|
user_model "code.gitea.io/gitea/models/user"
|
|
api "code.gitea.io/gitea/modules/structs"
|
|
"code.gitea.io/gitea/tests"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestAPIListProjects(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
|
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
|
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
|
|
|
token := getUserToken(t, owner.Name, auth_model.AccessTokenScopeReadIssue)
|
|
|
|
// Test listing all projects
|
|
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/projects", owner.Name, repo.Name).
|
|
AddTokenAuth(token)
|
|
resp := MakeRequest(t, req, http.StatusOK)
|
|
|
|
var projects []*api.Project
|
|
DecodeJSON(t, resp, &projects)
|
|
|
|
// Test state filter - open
|
|
req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/projects?state=open", owner.Name, repo.Name).
|
|
AddTokenAuth(token)
|
|
resp = MakeRequest(t, req, http.StatusOK)
|
|
DecodeJSON(t, resp, &projects)
|
|
for _, project := range projects {
|
|
assert.False(t, project.IsClosed, "Project should be open")
|
|
}
|
|
|
|
// Test state filter - all
|
|
req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/projects?state=all", owner.Name, repo.Name).
|
|
AddTokenAuth(token)
|
|
resp = MakeRequest(t, req, http.StatusOK)
|
|
DecodeJSON(t, resp, &projects)
|
|
|
|
// Test pagination
|
|
req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/projects?page=1&limit=5", owner.Name, repo.Name).
|
|
AddTokenAuth(token)
|
|
MakeRequest(t, req, http.StatusOK)
|
|
}
|
|
|
|
func TestAPIGetProject(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
|
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
|
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
|
|
|
// Create a test project
|
|
project := &project_model.Project{
|
|
Title: "Test Project for API",
|
|
RepoID: repo.ID,
|
|
Type: project_model.TypeRepository,
|
|
CreatorID: owner.ID,
|
|
TemplateType: project_model.TemplateTypeNone,
|
|
}
|
|
err := project_model.NewProject(t.Context(), project)
|
|
assert.NoError(t, err)
|
|
defer func() {
|
|
_ = project_model.DeleteProjectByID(t.Context(), project.ID)
|
|
}()
|
|
|
|
token := getUserToken(t, owner.Name, auth_model.AccessTokenScopeReadIssue)
|
|
|
|
// Test getting the project
|
|
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/projects/%d", owner.Name, repo.Name, project.ID).
|
|
AddTokenAuth(token)
|
|
resp := MakeRequest(t, req, http.StatusOK)
|
|
|
|
var apiProject api.Project
|
|
DecodeJSON(t, resp, &apiProject)
|
|
assert.Equal(t, project.Title, apiProject.Title)
|
|
assert.Equal(t, project.ID, apiProject.ID)
|
|
assert.Equal(t, repo.ID, apiProject.RepoID)
|
|
assert.NotEmpty(t, apiProject.URL)
|
|
|
|
// Test getting non-existent project
|
|
req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/projects/99999", owner.Name, repo.Name).
|
|
AddTokenAuth(token)
|
|
MakeRequest(t, req, http.StatusNotFound)
|
|
}
|
|
|
|
func TestAPICreateProject(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
|
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
|
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
|
|
|
token := getUserToken(t, owner.Name, auth_model.AccessTokenScopeWriteIssue)
|
|
|
|
// Test creating a project
|
|
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/projects", owner.Name, repo.Name), &api.CreateProjectOption{
|
|
Title: "API Created Project",
|
|
Description: "This is a test project created via API",
|
|
TemplateType: 1, // basic_kanban
|
|
CardType: 1, // images_and_text
|
|
}).AddTokenAuth(token)
|
|
resp := MakeRequest(t, req, http.StatusCreated)
|
|
|
|
var project api.Project
|
|
DecodeJSON(t, resp, &project)
|
|
assert.Equal(t, "API Created Project", project.Title)
|
|
assert.Equal(t, "This is a test project created via API", project.Description)
|
|
assert.Equal(t, 1, project.TemplateType)
|
|
assert.Equal(t, 1, project.CardType)
|
|
assert.False(t, project.IsClosed)
|
|
defer func() {
|
|
_ = project_model.DeleteProjectByID(t.Context(), project.ID)
|
|
}()
|
|
|
|
// Test creating with minimal data
|
|
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/projects", owner.Name, repo.Name), &api.CreateProjectOption{
|
|
Title: "Minimal Project",
|
|
}).AddTokenAuth(token)
|
|
resp = MakeRequest(t, req, http.StatusCreated)
|
|
|
|
var minimalProject api.Project
|
|
DecodeJSON(t, resp, &minimalProject)
|
|
assert.Equal(t, "Minimal Project", minimalProject.Title)
|
|
defer func() {
|
|
_ = project_model.DeleteProjectByID(t.Context(), minimalProject.ID)
|
|
}()
|
|
|
|
// Test creating without authentication
|
|
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/projects", owner.Name, repo.Name), &api.CreateProjectOption{
|
|
Title: "Unauthorized Project",
|
|
})
|
|
MakeRequest(t, req, http.StatusUnauthorized)
|
|
|
|
// Test creating with invalid data (empty title)
|
|
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/projects", owner.Name, repo.Name), &api.CreateProjectOption{
|
|
Title: "",
|
|
}).AddTokenAuth(token)
|
|
MakeRequest(t, req, http.StatusUnprocessableEntity)
|
|
}
|
|
|
|
func TestAPIUpdateProject(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
|
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
|
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
|
|
|
// Create a test project
|
|
project := &project_model.Project{
|
|
Title: "Project to Update",
|
|
Description: "Original description",
|
|
RepoID: repo.ID,
|
|
Type: project_model.TypeRepository,
|
|
CreatorID: owner.ID,
|
|
TemplateType: project_model.TemplateTypeNone,
|
|
}
|
|
err := project_model.NewProject(t.Context(), project)
|
|
assert.NoError(t, err)
|
|
defer func() {
|
|
_ = project_model.DeleteProjectByID(t.Context(), project.ID)
|
|
}()
|
|
|
|
token := getUserToken(t, owner.Name, auth_model.AccessTokenScopeWriteIssue)
|
|
|
|
// Test updating project title and description
|
|
newTitle := "Updated Project Title"
|
|
newDesc := "Updated description"
|
|
req := NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/repos/%s/%s/projects/%d", owner.Name, repo.Name, project.ID), &api.EditProjectOption{
|
|
Title: &newTitle,
|
|
Description: &newDesc,
|
|
}).AddTokenAuth(token)
|
|
resp := MakeRequest(t, req, http.StatusOK)
|
|
|
|
var updatedProject api.Project
|
|
DecodeJSON(t, resp, &updatedProject)
|
|
assert.Equal(t, newTitle, updatedProject.Title)
|
|
assert.Equal(t, newDesc, updatedProject.Description)
|
|
|
|
// Test closing project
|
|
isClosed := true
|
|
req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/repos/%s/%s/projects/%d", owner.Name, repo.Name, project.ID), &api.EditProjectOption{
|
|
IsClosed: &isClosed,
|
|
}).AddTokenAuth(token)
|
|
resp = MakeRequest(t, req, http.StatusOK)
|
|
|
|
DecodeJSON(t, resp, &updatedProject)
|
|
assert.True(t, updatedProject.IsClosed)
|
|
assert.NotNil(t, updatedProject.ClosedDate)
|
|
|
|
// Test reopening project
|
|
isClosed = false
|
|
req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/repos/%s/%s/projects/%d", owner.Name, repo.Name, project.ID), &api.EditProjectOption{
|
|
IsClosed: &isClosed,
|
|
}).AddTokenAuth(token)
|
|
resp = MakeRequest(t, req, http.StatusOK)
|
|
|
|
DecodeJSON(t, resp, &updatedProject)
|
|
assert.False(t, updatedProject.IsClosed)
|
|
|
|
// Test updating non-existent project
|
|
req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/repos/%s/%s/projects/99999", owner.Name, repo.Name), &api.EditProjectOption{
|
|
Title: &newTitle,
|
|
}).AddTokenAuth(token)
|
|
MakeRequest(t, req, http.StatusNotFound)
|
|
}
|
|
|
|
func TestAPIDeleteProject(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
|
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
|
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
|
|
|
// Create a test project
|
|
project := &project_model.Project{
|
|
Title: "Project to Delete",
|
|
RepoID: repo.ID,
|
|
Type: project_model.TypeRepository,
|
|
CreatorID: owner.ID,
|
|
TemplateType: project_model.TemplateTypeNone,
|
|
}
|
|
err := project_model.NewProject(t.Context(), project)
|
|
assert.NoError(t, err)
|
|
|
|
token := getUserToken(t, owner.Name, auth_model.AccessTokenScopeWriteIssue)
|
|
|
|
// Test deleting the project
|
|
req := NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/projects/%d", owner.Name, repo.Name, project.ID).
|
|
AddTokenAuth(token)
|
|
MakeRequest(t, req, http.StatusNoContent)
|
|
|
|
// Test deleting non-existent project (including the one we just deleted)
|
|
req = NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/projects/%d", owner.Name, repo.Name, project.ID).
|
|
AddTokenAuth(token)
|
|
MakeRequest(t, req, http.StatusNotFound)
|
|
}
|
|
|
|
func TestAPIListProjectColumns(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
|
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
|
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
|
|
|
// Create a test project
|
|
project := &project_model.Project{
|
|
Title: "Project for Columns Test",
|
|
RepoID: repo.ID,
|
|
Type: project_model.TypeRepository,
|
|
CreatorID: owner.ID,
|
|
TemplateType: project_model.TemplateTypeNone,
|
|
}
|
|
err := project_model.NewProject(t.Context(), project)
|
|
assert.NoError(t, err)
|
|
defer func() {
|
|
_ = project_model.DeleteProjectByID(t.Context(), project.ID)
|
|
}()
|
|
|
|
// Create test columns
|
|
for i := 1; i <= 3; i++ {
|
|
column := &project_model.Column{
|
|
Title: fmt.Sprintf("Column %d", i),
|
|
ProjectID: project.ID,
|
|
CreatorID: owner.ID,
|
|
}
|
|
err = project_model.NewColumn(t.Context(), column)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
token := getUserToken(t, owner.Name, auth_model.AccessTokenScopeReadIssue)
|
|
|
|
// Test listing columns
|
|
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/projects/%d/columns", owner.Name, repo.Name, project.ID).
|
|
AddTokenAuth(token)
|
|
resp := MakeRequest(t, req, http.StatusOK)
|
|
|
|
var columns []*api.ProjectColumn
|
|
DecodeJSON(t, resp, &columns)
|
|
assert.Len(t, columns, 3)
|
|
assert.Equal(t, "Column 1", columns[0].Title)
|
|
assert.Equal(t, "Column 2", columns[1].Title)
|
|
assert.Equal(t, "Column 3", columns[2].Title)
|
|
|
|
// Test pagination
|
|
req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/projects/%d/columns?page=1&limit=2", owner.Name, repo.Name, project.ID).
|
|
AddTokenAuth(token)
|
|
resp = MakeRequest(t, req, http.StatusOK)
|
|
DecodeJSON(t, resp, &columns)
|
|
assert.Len(t, columns, 2)
|
|
|
|
// Test listing columns for non-existent project
|
|
req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/projects/99999/columns", owner.Name, repo.Name).
|
|
AddTokenAuth(token)
|
|
MakeRequest(t, req, http.StatusNotFound)
|
|
}
|
|
|
|
func TestAPICreateProjectColumn(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
|
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
|
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
|
|
|
// Create a test project
|
|
project := &project_model.Project{
|
|
Title: "Project for Column Creation",
|
|
RepoID: repo.ID,
|
|
Type: project_model.TypeRepository,
|
|
CreatorID: owner.ID,
|
|
TemplateType: project_model.TemplateTypeNone,
|
|
}
|
|
err := project_model.NewProject(t.Context(), project)
|
|
assert.NoError(t, err)
|
|
defer func() {
|
|
_ = project_model.DeleteProjectByID(t.Context(), project.ID)
|
|
}()
|
|
|
|
token := getUserToken(t, owner.Name, auth_model.AccessTokenScopeWriteIssue)
|
|
|
|
// Test creating a column with color
|
|
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/projects/%d/columns", owner.Name, repo.Name, project.ID), &api.CreateProjectColumnOption{
|
|
Title: "New Column",
|
|
Color: "#FF5733",
|
|
}).AddTokenAuth(token)
|
|
resp := MakeRequest(t, req, http.StatusCreated)
|
|
|
|
var column api.ProjectColumn
|
|
DecodeJSON(t, resp, &column)
|
|
assert.Equal(t, "New Column", column.Title)
|
|
assert.Equal(t, "#FF5733", column.Color)
|
|
assert.Equal(t, project.ID, column.ProjectID)
|
|
|
|
// Test creating a column without color
|
|
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/projects/%d/columns", owner.Name, repo.Name, project.ID), &api.CreateProjectColumnOption{
|
|
Title: "Simple Column",
|
|
}).AddTokenAuth(token)
|
|
resp = MakeRequest(t, req, http.StatusCreated)
|
|
|
|
DecodeJSON(t, resp, &column)
|
|
assert.Equal(t, "Simple Column", column.Title)
|
|
|
|
// Test creating with empty title
|
|
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/projects/%d/columns", owner.Name, repo.Name, project.ID), &api.CreateProjectColumnOption{
|
|
Title: "",
|
|
}).AddTokenAuth(token)
|
|
MakeRequest(t, req, http.StatusUnprocessableEntity)
|
|
|
|
// Test creating for non-existent project
|
|
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/projects/99999/columns", owner.Name, repo.Name), &api.CreateProjectColumnOption{
|
|
Title: "Orphan Column",
|
|
}).AddTokenAuth(token)
|
|
MakeRequest(t, req, http.StatusNotFound)
|
|
}
|
|
|
|
func TestAPIUpdateProjectColumn(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
|
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
|
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
|
|
|
// Create a test project and column
|
|
project := &project_model.Project{
|
|
Title: "Project for Column Update",
|
|
RepoID: repo.ID,
|
|
Type: project_model.TypeRepository,
|
|
CreatorID: owner.ID,
|
|
TemplateType: project_model.TemplateTypeNone,
|
|
}
|
|
err := project_model.NewProject(t.Context(), project)
|
|
assert.NoError(t, err)
|
|
defer func() {
|
|
_ = project_model.DeleteProjectByID(t.Context(), project.ID)
|
|
}()
|
|
|
|
column := &project_model.Column{
|
|
Title: "Original Column",
|
|
ProjectID: project.ID,
|
|
CreatorID: owner.ID,
|
|
Color: "#000000",
|
|
}
|
|
err = project_model.NewColumn(t.Context(), column)
|
|
assert.NoError(t, err)
|
|
|
|
token := getUserToken(t, owner.Name, auth_model.AccessTokenScopeWriteIssue)
|
|
|
|
// Test updating column title
|
|
newTitle := "Updated Column"
|
|
req := NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/repos/%s/%s/projects/columns/%d", owner.Name, repo.Name, column.ID), &api.EditProjectColumnOption{
|
|
Title: &newTitle,
|
|
}).AddTokenAuth(token)
|
|
resp := MakeRequest(t, req, http.StatusOK)
|
|
|
|
var updatedColumn api.ProjectColumn
|
|
DecodeJSON(t, resp, &updatedColumn)
|
|
assert.Equal(t, newTitle, updatedColumn.Title)
|
|
|
|
// Test updating column color
|
|
newColor := "#FF0000"
|
|
req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/repos/%s/%s/projects/columns/%d", owner.Name, repo.Name, column.ID), &api.EditProjectColumnOption{
|
|
Color: &newColor,
|
|
}).AddTokenAuth(token)
|
|
resp = MakeRequest(t, req, http.StatusOK)
|
|
|
|
DecodeJSON(t, resp, &updatedColumn)
|
|
assert.Equal(t, newColor, updatedColumn.Color)
|
|
|
|
// Test updating non-existent column
|
|
req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/repos/%s/%s/projects/columns/99999", owner.Name, repo.Name), &api.EditProjectColumnOption{
|
|
Title: &newTitle,
|
|
}).AddTokenAuth(token)
|
|
MakeRequest(t, req, http.StatusNotFound)
|
|
}
|
|
|
|
func TestAPIDeleteProjectColumn(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
|
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
|
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
|
|
|
// Create a test project and column
|
|
project := &project_model.Project{
|
|
Title: "Project for Column Deletion",
|
|
RepoID: repo.ID,
|
|
Type: project_model.TypeRepository,
|
|
CreatorID: owner.ID,
|
|
TemplateType: project_model.TemplateTypeNone,
|
|
}
|
|
err := project_model.NewProject(t.Context(), project)
|
|
assert.NoError(t, err)
|
|
defer func() {
|
|
_ = project_model.DeleteProjectByID(t.Context(), project.ID)
|
|
}()
|
|
|
|
column := &project_model.Column{
|
|
Title: "Column to Delete",
|
|
ProjectID: project.ID,
|
|
CreatorID: owner.ID,
|
|
}
|
|
err = project_model.NewColumn(t.Context(), column)
|
|
assert.NoError(t, err)
|
|
|
|
token := getUserToken(t, owner.Name, auth_model.AccessTokenScopeWriteIssue)
|
|
|
|
// Test deleting the column
|
|
req := NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/projects/columns/%d", owner.Name, repo.Name, column.ID).
|
|
AddTokenAuth(token)
|
|
MakeRequest(t, req, http.StatusNoContent)
|
|
|
|
// Test deleting non-existent column (including the one we just deleted)
|
|
req = NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/projects/columns/%d", owner.Name, repo.Name, column.ID).
|
|
AddTokenAuth(token)
|
|
MakeRequest(t, req, http.StatusNotFound)
|
|
}
|
|
|
|
func TestAPIAddIssueToProjectColumn(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
|
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
|
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
|
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: repo.ID})
|
|
|
|
// Create a test project and column
|
|
project := &project_model.Project{
|
|
Title: "Project for Issue Assignment",
|
|
RepoID: repo.ID,
|
|
Type: project_model.TypeRepository,
|
|
CreatorID: owner.ID,
|
|
TemplateType: project_model.TemplateTypeNone,
|
|
}
|
|
err := project_model.NewProject(t.Context(), project)
|
|
assert.NoError(t, err)
|
|
defer func() {
|
|
_ = project_model.DeleteProjectByID(t.Context(), project.ID)
|
|
}()
|
|
|
|
column1 := &project_model.Column{
|
|
Title: "Column 1",
|
|
ProjectID: project.ID,
|
|
CreatorID: owner.ID,
|
|
}
|
|
err = project_model.NewColumn(t.Context(), column1)
|
|
assert.NoError(t, err)
|
|
|
|
column2 := &project_model.Column{
|
|
Title: "Column 2",
|
|
ProjectID: project.ID,
|
|
CreatorID: owner.ID,
|
|
}
|
|
err = project_model.NewColumn(t.Context(), column2)
|
|
assert.NoError(t, err)
|
|
|
|
token := getUserToken(t, owner.Name, auth_model.AccessTokenScopeWriteIssue)
|
|
|
|
// Test adding issue to column
|
|
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/projects/columns/%d/issues", owner.Name, repo.Name, column1.ID), &api.AddIssueToProjectColumnOption{
|
|
IssueID: issue.ID,
|
|
}).AddTokenAuth(token)
|
|
MakeRequest(t, req, http.StatusCreated)
|
|
|
|
// Verify issue is in the column
|
|
projectIssue := unittest.AssertExistsAndLoadBean(t, &project_model.ProjectIssue{
|
|
ProjectID: project.ID,
|
|
IssueID: issue.ID,
|
|
})
|
|
assert.Equal(t, column1.ID, projectIssue.ProjectColumnID)
|
|
|
|
// Test moving issue to another column
|
|
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/projects/columns/%d/issues", owner.Name, repo.Name, column2.ID), &api.AddIssueToProjectColumnOption{
|
|
IssueID: issue.ID,
|
|
}).AddTokenAuth(token)
|
|
MakeRequest(t, req, http.StatusCreated)
|
|
|
|
// Verify issue moved to new column
|
|
projectIssue = unittest.AssertExistsAndLoadBean(t, &project_model.ProjectIssue{
|
|
ProjectID: project.ID,
|
|
IssueID: issue.ID,
|
|
})
|
|
assert.Equal(t, column2.ID, projectIssue.ProjectColumnID)
|
|
|
|
// Test adding same issue to same column (should be idempotent)
|
|
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/projects/columns/%d/issues", owner.Name, repo.Name, column2.ID), &api.AddIssueToProjectColumnOption{
|
|
IssueID: issue.ID,
|
|
}).AddTokenAuth(token)
|
|
MakeRequest(t, req, http.StatusCreated)
|
|
|
|
// Test adding non-existent issue
|
|
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/projects/columns/%d/issues", owner.Name, repo.Name, column1.ID), &api.AddIssueToProjectColumnOption{
|
|
IssueID: 99999,
|
|
}).AddTokenAuth(token)
|
|
MakeRequest(t, req, http.StatusUnprocessableEntity)
|
|
|
|
// Test adding to non-existent column
|
|
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/projects/columns/99999/issues", owner.Name, repo.Name), &api.AddIssueToProjectColumnOption{
|
|
IssueID: issue.ID,
|
|
}).AddTokenAuth(token)
|
|
MakeRequest(t, req, http.StatusNotFound)
|
|
}
|
|
|
|
func TestAPIProjectPermissions(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
|
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
|
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
|
nonCollaborator := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user5"})
|
|
|
|
// Create a test project
|
|
project := &project_model.Project{
|
|
Title: "Permission Test Project",
|
|
RepoID: repo.ID,
|
|
Type: project_model.TypeRepository,
|
|
CreatorID: owner.ID,
|
|
TemplateType: project_model.TemplateTypeNone,
|
|
}
|
|
err := project_model.NewProject(t.Context(), project)
|
|
assert.NoError(t, err)
|
|
defer func() {
|
|
_ = project_model.DeleteProjectByID(t.Context(), project.ID)
|
|
}()
|
|
|
|
ownerToken := getUserToken(t, owner.Name, auth_model.AccessTokenScopeWriteIssue)
|
|
nonCollaboratorToken := getUserToken(t, nonCollaborator.Name, auth_model.AccessTokenScopeWriteIssue)
|
|
|
|
// Owner should be able to read
|
|
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/projects/%d", owner.Name, repo.Name, project.ID).
|
|
AddTokenAuth(ownerToken)
|
|
MakeRequest(t, req, http.StatusOK)
|
|
|
|
// Owner should be able to update
|
|
newTitle := "Updated by Owner"
|
|
req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/repos/%s/%s/projects/%d", owner.Name, repo.Name, project.ID), &api.EditProjectOption{
|
|
Title: &newTitle,
|
|
}).AddTokenAuth(ownerToken)
|
|
MakeRequest(t, req, http.StatusOK)
|
|
|
|
// Non-collaborator should not be able to update
|
|
anotherTitle := "Updated by Non-collaborator"
|
|
req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/repos/%s/%s/projects/%d", owner.Name, repo.Name, project.ID), &api.EditProjectOption{
|
|
Title: &anotherTitle,
|
|
}).AddTokenAuth(nonCollaboratorToken)
|
|
MakeRequest(t, req, http.StatusForbidden)
|
|
|
|
// Non-collaborator should not be able to delete
|
|
req = NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/projects/%d", owner.Name, repo.Name, project.ID).
|
|
AddTokenAuth(nonCollaboratorToken)
|
|
MakeRequest(t, req, http.StatusForbidden)
|
|
}
|