1cd81ff925
Adds two improvements to the user/org/repo project-board REST API: * state filter on column-issues endpoints (issue #4) GET /api/v1/{users,orgs}/{name}/-/projects/{id}/columns/{col}/issues GET /api/v1/repos/{owner}/{repo}/projects/{id}/columns/{col}/issues Now accept ?state=open|closed|all (default open), matching the convention on the project-list endpoint and on /repos/.../issues. Applied at the IssuesOptions layer so all three scopes inherit the filter. * populated num_issues / num_open_issues / num_closed_issues on column-list (issue #5) ColumnList.LoadIssueCounts runs two grouped queries against project_issue joined with issue (one open, one closed). All three List*Columns handlers call it before converting, so num_issues stops being null and consumers can render a kanban summary in a single round trip instead of N+1. Tests: * unit: empty-input fast path on ColumnList.LoadIssueCounts. * integration: extended testAPIListProjectColumnIssues / -User / -Org to close an issue, then verify default=open hides it, state=closed and state=all return it, and the column-list response carries the correct open/closed/total split. Closes #4, closes #5
76 lines
2.4 KiB
Go
76 lines
2.4 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package project
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"code.gitea.io/gitea/models/db"
|
|
"code.gitea.io/gitea/models/unittest"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestProjectColumns(t *testing.T) {
|
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
|
t.Run("CountProjectColumns", testCountProjectColumns)
|
|
t.Run("GetProjectColumns", testGetProjectColumns)
|
|
t.Run("GetColumnsByIDs", testGetColumnsByIDs)
|
|
t.Run("LoadIssueCountsEmpty", testLoadIssueCountsEmpty)
|
|
}
|
|
|
|
func testCountProjectColumns(t *testing.T) {
|
|
project, err := GetProjectByID(t.Context(), 1)
|
|
assert.NoError(t, err)
|
|
|
|
count, err := CountProjectColumns(t.Context(), project.ID)
|
|
assert.NoError(t, err)
|
|
assert.EqualValues(t, 3, count)
|
|
}
|
|
|
|
func testGetProjectColumns(t *testing.T) {
|
|
project, err := GetProjectByID(t.Context(), 1)
|
|
assert.NoError(t, err)
|
|
|
|
// Page 1, limit 2 — returns first 2 columns
|
|
page1, err := GetProjectColumns(t.Context(), project.ID, db.ListOptions{Page: 1, PageSize: 2})
|
|
assert.NoError(t, err)
|
|
assert.Len(t, page1, 2)
|
|
|
|
// Page 2, limit 2 — returns remaining column
|
|
page2, err := GetProjectColumns(t.Context(), project.ID, db.ListOptions{Page: 2, PageSize: 2})
|
|
assert.NoError(t, err)
|
|
assert.Len(t, page2, 1)
|
|
|
|
// Page 1 and page 2 together cover all columns with no overlap
|
|
allIDs := make(map[int64]bool)
|
|
for _, c := range append(page1, page2...) {
|
|
assert.False(t, allIDs[c.ID], "duplicate column ID %d across pages", c.ID)
|
|
allIDs[c.ID] = true
|
|
}
|
|
assert.Len(t, allIDs, 3)
|
|
}
|
|
|
|
func testLoadIssueCountsEmpty(t *testing.T) {
|
|
// Empty input is a fast path — must not touch the database and must not error.
|
|
// (The full open/closed-count behavior is exercised by the integration tests
|
|
// in tests/integration/api_*_project_test.go, which can join against the issue
|
|
// table; the unit-test fixture set here intentionally excludes it.)
|
|
assert.NoError(t, ColumnList{}.LoadIssueCounts(t.Context()))
|
|
}
|
|
|
|
func testGetColumnsByIDs(t *testing.T) {
|
|
project, err := GetProjectByID(t.Context(), 1)
|
|
assert.NoError(t, err)
|
|
|
|
columns, err := GetColumnsByIDs(t.Context(), project.ID, []int64{1, 3, 4})
|
|
assert.NoError(t, err)
|
|
assert.Len(t, columns, 2)
|
|
assert.ElementsMatch(t, []int64{1, 3}, []int64{columns[0].ID, columns[1].ID})
|
|
|
|
empty, err := GetColumnsByIDs(t.Context(), project.ID, nil)
|
|
assert.NoError(t, err)
|
|
assert.Empty(t, empty)
|
|
}
|