This is an automated email from the ASF dual-hosted git repository.
klesh pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/devlake.git
The following commit(s) were added to refs/heads/main by this push:
new e905b898b fix(testmo): read the page count from the top-level
last_page (#9333)
e905b898b is described below
commit e905b898b1d077d853927b783a2c9b5669e22393
Author: Shivam Sharma <[email protected]>
AuthorDate: Sun Nov 8 19:31:50 2026 +0530
fix(testmo): read the page count from the top-level last_page (#9333)
GetTotalPagesFromResponse read meta.total_pages, but Testmo list
responses (RunPage, MilestonePage, AutomationRunPage) have no meta
object: page, next_page, last_page, per_page and total are top-level
fields. The function always returned 1, so collectRuns,
collectMilestones and collectAutomationRuns only collected the first
100 items of a project. Read last_page instead.
Closes #9332
Generated-by: Claude Code (Claude Opus 5.5)
Signed-off-by: Shivam Sharma
<[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
---
backend/plugins/testmo/tasks/shared.go | 18 ++++----
backend/plugins/testmo/tasks/shared_test.go | 65 +++++++++++++++++++++++++++++
2 files changed, 74 insertions(+), 9 deletions(-)
diff --git a/backend/plugins/testmo/tasks/shared.go
b/backend/plugins/testmo/tasks/shared.go
index 3a2e86dd5..2d8187d7e 100644
--- a/backend/plugins/testmo/tasks/shared.go
+++ b/backend/plugins/testmo/tasks/shared.go
@@ -45,20 +45,20 @@ func CreateTestmoApiClient(taskCtx plugin.TaskContext,
connection *models.Testmo
return CreateApiClient(taskCtx, connection)
}
+// GetTotalPagesFromResponse reads the page count of a Testmo list response.
+// Testmo returns the pagination fields (page, prev_page, next_page,
+// last_page, per_page, total) at the top level of the body, not in a meta
+// object; last_page is nullable.
func GetTotalPagesFromResponse(res *http.Response, args
*helper.ApiCollectorArgs) (int, errors.Error) {
- body := &models.TestmoResponse{}
+ body := &struct {
+ LastPage *int `json:"last_page"`
+ }{}
err := helper.UnmarshalResponse(res, body)
if err != nil {
return 0, err
}
-
- // Parse meta information for pagination
- if meta, ok := body.Meta.(map[string]interface{}); ok {
- if totalPages, exists := meta["total_pages"]; exists {
- if pages, ok := totalPages.(float64); ok {
- return int(pages), nil
- }
- }
+ if body.LastPage != nil && *body.LastPage > 1 {
+ return *body.LastPage, nil
}
return 1, nil
}
diff --git a/backend/plugins/testmo/tasks/shared_test.go
b/backend/plugins/testmo/tasks/shared_test.go
new file mode 100644
index 000000000..c98a03738
--- /dev/null
+++ b/backend/plugins/testmo/tasks/shared_test.go
@@ -0,0 +1,65 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package tasks
+
+import (
+ "io"
+ "net/http"
+ "strings"
+ "testing"
+
+ helper "github.com/apache/devlake/helpers/pluginhelper/api"
+ "github.com/stretchr/testify/assert"
+)
+
+func newTestmoListResponse(body string) *http.Response {
+ return &http.Response{
+ StatusCode: http.StatusOK,
+ Body: io.NopCloser(strings.NewReader(body)),
+ }
+}
+
+func TestGetTotalPagesFromResponse(t *testing.T) {
+ args := &helper.ApiCollectorArgs{PageSize: 100}
+
+ t.Run("first of several pages", func(t *testing.T) {
+ // Testmo list endpoints (RunPage, MilestonePage,
AutomationRunPage)
+ // return the pagination fields at the top level.
+ res := newTestmoListResponse(`{"page": 1, "prev_page": null,
"next_page": 2, "last_page": 3,
+ "per_page": 100, "total": 250, "result": [{"id": 1}]}`)
+ totalPages, err := GetTotalPagesFromResponse(res, args)
+ assert.Nil(t, err)
+ assert.Equal(t, 3, totalPages)
+ })
+
+ t.Run("single page", func(t *testing.T) {
+ res := newTestmoListResponse(`{"page": 1, "prev_page": null,
"next_page": null, "last_page": 1,
+ "per_page": 100, "total": 2, "result": [{"id": 1},
{"id": 2}]}`)
+ totalPages, err := GetTotalPagesFromResponse(res, args)
+ assert.Nil(t, err)
+ assert.Equal(t, 1, totalPages)
+ })
+
+ t.Run("no results", func(t *testing.T) {
+ res := newTestmoListResponse(`{"page": null, "prev_page": null,
"next_page": null, "last_page": null,
+ "per_page": 100, "total": 0, "result": []}`)
+ totalPages, err := GetTotalPagesFromResponse(res, args)
+ assert.Nil(t, err)
+ assert.Equal(t, 1, totalPages)
+ })
+}