Skip to content

Commit df12fe3

Browse files
committed
fix: Fix project board API test failures
1. Fix DeleteProject to return 404 for non-existent projects - Add project existence check before deletion - Ensures idempotent DELETE behavior per REST standards 2. Fix ListProjectColumns pagination - Implement manual pagination with page/limit parameters - Add Link and TotalCount headers for proper pagination - Add swagger documentation for pagination params 3. Add issue validation to AddIssueToProjectColumn - Verify issue exists and belongs to the repository - Return 422 Unprocessable Entity for invalid issues - Improves API security and error handling These fixes address the failing integration tests: - TestAPIDeleteProject - TestAPIListProjectColumns - TestAPIAddIssueToProjectColumn (partial)
1 parent f8c1219 commit df12fe3

File tree

1 file changed

+64
-2
lines changed

1 file changed

+64
-2
lines changed

routers/api/v1/repo/project.go

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"net/http"
88

99
"code.gitea.io/gitea/models/db"
10+
issues_model "code.gitea.io/gitea/models/issues"
1011
project_model "code.gitea.io/gitea/models/project"
1112
"code.gitea.io/gitea/models/unit"
1213
"code.gitea.io/gitea/modules/optional"
@@ -342,7 +343,9 @@ func DeleteProject(ctx *context.APIContext) {
342343
return
343344
}
344345

345-
if err := project_model.DeleteProjectByID(ctx, ctx.PathParamInt64("id")); err != nil {
346+
// Verify project exists and belongs to this repository
347+
project, err := project_model.GetProjectForRepoByID(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("id"))
348+
if err != nil {
346349
if project_model.IsErrProjectNotExist(err) {
347350
ctx.APIErrorNotFound()
348351
} else {
@@ -351,6 +354,11 @@ func DeleteProject(ctx *context.APIContext) {
351354
return
352355
}
353356

357+
if err := project_model.DeleteProjectByID(ctx, project.ID); err != nil {
358+
ctx.APIErrorInternal(err)
359+
return
360+
}
361+
354362
ctx.Status(http.StatusNoContent)
355363
}
356364

@@ -378,6 +386,14 @@ func ListProjectColumns(ctx *context.APIContext) {
378386
// type: integer
379387
// format: int64
380388
// required: true
389+
// - name: page
390+
// in: query
391+
// description: page number of results
392+
// type: integer
393+
// - name: limit
394+
// in: query
395+
// description: page size of results
396+
// type: integer
381397
// responses:
382398
// "200":
383399
// "$ref": "#/responses/ProjectColumnList"
@@ -399,12 +415,42 @@ func ListProjectColumns(ctx *context.APIContext) {
399415
return
400416
}
401417

402-
columns, err := project.GetColumns(ctx)
418+
// Get all columns
419+
allColumns, err := project.GetColumns(ctx)
403420
if err != nil {
404421
ctx.APIErrorInternal(err)
405422
return
406423
}
407424

425+
totalCount := int64(len(allColumns))
426+
427+
// Parse pagination parameters
428+
page := ctx.FormInt("page")
429+
if page <= 0 {
430+
page = 1
431+
}
432+
433+
limit := ctx.FormInt("limit")
434+
if limit <= 0 {
435+
limit = setting.UI.IssuePagingNum
436+
}
437+
438+
// Apply pagination
439+
start := (page - 1) * limit
440+
end := start + limit
441+
442+
var columns project_model.ColumnList
443+
if start < len(allColumns) {
444+
if end > len(allColumns) {
445+
end = len(allColumns)
446+
}
447+
columns = allColumns[start:end]
448+
} else {
449+
columns = make([]*project_model.Column, 0)
450+
}
451+
452+
ctx.SetLinkHeader(int(totalCount), limit)
453+
ctx.SetTotalCountHeader(totalCount)
408454
ctx.JSON(http.StatusOK, convert.ToProjectColumnList(ctx, columns))
409455
}
410456

@@ -700,6 +746,22 @@ func AddIssueToProjectColumn(ctx *context.APIContext) {
700746
// Parse request body
701747
form := web.GetForm(ctx).(*api.AddIssueToProjectColumnOption)
702748

749+
// Verify issue exists and belongs to this repository
750+
issue, err := issues_model.GetIssueByID(ctx, form.IssueID)
751+
if err != nil {
752+
if issues_model.IsErrIssueNotExist(err) {
753+
ctx.APIError(http.StatusUnprocessableEntity, "issue not found")
754+
} else {
755+
ctx.APIErrorInternal(err)
756+
}
757+
return
758+
}
759+
760+
if issue.RepoID != ctx.Repo.Repository.ID {
761+
ctx.APIError(http.StatusUnprocessableEntity, "issue does not belong to this repository")
762+
return
763+
}
764+
703765
// Add issue to column
704766
if err := project_model.AddIssueToColumn(ctx, form.IssueID, column); err != nil {
705767
ctx.APIErrorInternal(err)

0 commit comments

Comments
 (0)