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)
+       })
+}

Reply via email to