chore: document and test pagination of /runners API endpoint (#10551)

Document the pagination of all the `/runners` API endpoints and add tests for them. Follow-up of https://codeberg.org/forgejo/forgejo/pulls/10450.

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/10551
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
Co-authored-by: Andreas Ahlenstorf <andreas@ahlenstorf.ch>
Co-committed-by: Andreas Ahlenstorf <andreas@ahlenstorf.ch>
This commit is contained in:
Andreas Ahlenstorf 2025-12-22 23:15:55 +01:00 committed by Gusted
commit 537a802125
11 changed files with 160 additions and 1 deletions

View file

@ -50,6 +50,15 @@ func ListRunners(ctx *context.APIContext) {
// summary: Get all runners, no matter whether they are global runners or scoped to an organization, user, or repository
// produces:
// - application/json
// parameters:
// - name: page
// in: query
// description: page number of results to return (1-based)
// type: integer
// - name: limit
// in: query
// description: page size of results
// type: integer
// responses:
// "200":
// "$ref": "#/responses/ActionRunnerList"

View file

@ -278,6 +278,14 @@ func (Action) ListRunners(ctx *context.APIContext) {
// description: name of the organization
// type: string
// required: true
// - name: page
// in: query
// description: page number of results to return (1-based)
// type: integer
// - name: limit
// in: query
// description: page size of results
// type: integer
// responses:
// "200":
// "$ref": "#/responses/ActionRunnerList"

View file

@ -523,6 +523,14 @@ func (Action) ListRunners(ctx *context.APIContext) {
// description: name of the repo
// type: string
// required: true
// - name: page
// in: query
// description: page number of results to return (1-based)
// type: integer
// - name: limit
// in: query
// description: page size of results
// type: integer
// responses:
// "200":
// "$ref": "#/responses/ActionRunnerList"

View file

@ -87,10 +87,12 @@ func ListRunners(ctx *context.APIContext, ownerID, repoID int64) {
ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("ownerID and repoID should not be both set: %d and %d", ownerID, repoID))
return
}
listOptions := utils.GetListOptions(ctx)
runners, total, err := db.FindAndCount[actions_model.ActionRunner](ctx, &actions_model.FindRunnerOptions{
OwnerID: ownerID,
RepoID: repoID,
ListOptions: utils.GetListOptions(ctx),
ListOptions: listOptions,
})
if err != nil {
ctx.Error(http.StatusInternalServerError, "FindCountRunners", map[string]string{})
@ -106,6 +108,8 @@ func ListRunners(ctx *context.APIContext, ownerID, repoID int64) {
}
runnerList[i] = actionRunner
}
ctx.SetLinkHeader(int(total), listOptions.PageSize)
ctx.SetTotalCountHeader(total)
ctx.JSON(http.StatusOK, &runnerList)
}

View file

@ -69,4 +69,10 @@ type swaggerActionRunner struct {
type swaggerActionRunnerListResponse struct {
// in:body
Body []api.ActionRunner `json:"body"`
// Total number of runners matching the search criteria (excluding page and limit)
TotalCount int64 `json:"X-Total-Count"`
// Links to other pages, if any
Link string `json:"Link"`
}

View file

@ -56,6 +56,15 @@ func ListRunners(ctx *context.APIContext) {
// summary: Get the user's runners
// produces:
// - application/json
// parameters:
// - name: page
// in: query
// description: page number of results to return (1-based)
// type: integer
// - name: limit
// in: query
// description: page size of results
// type: integer
// responses:
// "200":
// "$ref": "#/responses/ActionRunnerList"

View file

@ -321,6 +321,20 @@
],
"summary": "Get all runners, no matter whether they are global runners or scoped to an organization, user, or repository",
"operationId": "getAdminRunners",
"parameters": [
{
"type": "integer",
"description": "page number of results to return (1-based)",
"name": "page",
"in": "query"
},
{
"type": "integer",
"description": "page size of results",
"name": "limit",
"in": "query"
}
],
"responses": {
"200": {
"$ref": "#/responses/ActionRunnerList"
@ -2627,6 +2641,18 @@
"name": "org",
"in": "path",
"required": true
},
{
"type": "integer",
"description": "page number of results to return (1-based)",
"name": "page",
"in": "query"
},
{
"type": "integer",
"description": "page size of results",
"name": "limit",
"in": "query"
}
],
"responses": {
@ -5308,6 +5334,18 @@
"name": "repo",
"in": "path",
"required": true
},
{
"type": "integer",
"description": "page number of results to return (1-based)",
"name": "page",
"in": "query"
},
{
"type": "integer",
"description": "page size of results",
"name": "limit",
"in": "query"
}
],
"responses": {
@ -18737,6 +18775,20 @@
],
"summary": "Get the user's runners",
"operationId": "getUserRunners",
"parameters": [
{
"type": "integer",
"description": "page number of results to return (1-based)",
"name": "page",
"in": "query"
},
{
"type": "integer",
"description": "page size of results",
"name": "limit",
"in": "query"
}
],
"responses": {
"200": {
"$ref": "#/responses/ActionRunnerList"
@ -29849,6 +29901,17 @@
"items": {
"$ref": "#/definitions/ActionRunner"
}
},
"headers": {
"Link": {
"type": "string",
"description": "Links to other pages, if any"
},
"X-Total-Count": {
"type": "integer",
"format": "int64",
"description": "Total number of runners matching the search criteria (excluding page and limit)"
}
}
},
"ActionVariable": {

View file

@ -177,6 +177,19 @@ func TestAPIGlobalActionsRunnerOperations(t *testing.T) {
assert.Contains(t, runners, runnerThree)
})
t.Run("GetRunnersPaginated", func(t *testing.T) {
request := NewRequest(t, "GET", "/api/v1/admin/actions/runners?page=1&limit=5")
request.AddTokenAuth(readToken)
response := MakeRequest(t, request, http.StatusOK)
var runners []*api.ActionRunner
DecodeJSON(t, response, &runners)
assert.NotEmpty(t, response.Header().Get("Link"))
assert.NotEmpty(t, response.Header().Get("X-Total-Count"))
assert.Len(t, runners, 5)
})
t.Run("GetGlobalRunner", func(t *testing.T) {
request := NewRequest(t, "GET", "/api/v1/admin/actions/runners/130793")
request.AddTokenAuth(readToken)

View file

@ -147,6 +147,19 @@ func TestAPIOrgActionsRunnerOperations(t *testing.T) {
assert.ElementsMatch(t, []*api.ActionRunner{runnerOne, runnerThree}, runners)
})
t.Run("GetRunnersPaginated", func(t *testing.T) {
request := NewRequest(t, "GET", "/api/v1/orgs/org3/actions/runners?page=1&limit=1")
request.AddTokenAuth(readToken)
response := MakeRequest(t, request, http.StatusOK)
var runners []*api.ActionRunner
DecodeJSON(t, response, &runners)
assert.NotEmpty(t, response.Header().Get("Link"))
assert.NotEmpty(t, response.Header().Get("X-Total-Count"))
assert.Len(t, runners, 1)
})
t.Run("GetRunner", func(t *testing.T) {
request := NewRequest(t, "GET", "/api/v1/orgs/org3/actions/runners/655691")
request.AddTokenAuth(readToken)

View file

@ -420,6 +420,19 @@ func TestAPIRepoActionsRunnerOperations(t *testing.T) {
assert.ElementsMatch(t, []*api.ActionRunner{runnerOne, runnerThree}, runners)
})
t.Run("GetRunnersPaginated", func(t *testing.T) {
request := NewRequest(t, "GET", "/api/v1/repos/user2/test_workflows/actions/runners?page=1&limit=1")
request.AddTokenAuth(readToken)
response := MakeRequest(t, request, http.StatusOK)
var runners []*api.ActionRunner
DecodeJSON(t, response, &runners)
assert.NotEmpty(t, response.Header().Get("Link"))
assert.NotEmpty(t, response.Header().Get("X-Total-Count"))
assert.Len(t, runners, 1)
})
t.Run("GetRunner", func(t *testing.T) {
request := NewRequest(t, "GET", "/api/v1/repos/user2/test_workflows/actions/runners/899251")
request.AddTokenAuth(readToken)

View file

@ -145,6 +145,19 @@ func TestAPIUserActionsRunnerOperations(t *testing.T) {
assert.ElementsMatch(t, []*api.ActionRunner{runnerOne, runnerThree}, runners)
})
t.Run("GetRunnersPaginated", func(t *testing.T) {
request := NewRequest(t, "GET", "/api/v1/user/actions/runners?page=1&limit=1")
request.AddTokenAuth(readToken)
response := MakeRequest(t, request, http.StatusOK)
var runners []*api.ActionRunner
DecodeJSON(t, response, &runners)
assert.NotEmpty(t, response.Header().Get("Link"))
assert.NotEmpty(t, response.Header().Get("X-Total-Count"))
assert.Len(t, runners, 1)
})
t.Run("GetRunner", func(t *testing.T) {
request := NewRequest(t, "GET", "/api/v1/user/actions/runners/71303")
request.AddTokenAuth(readToken)