Skip to content

Commit 12b3eba

Browse files
authored
Refactor item and launch sorting; improve error handling in API calls (#8)
* Refactor item and launch sorting; improve error handling in API calls * Add context utilities and middleware for handling query parameters
1 parent 8aad489 commit 12b3eba

File tree

10 files changed

+156
-12
lines changed

10 files changed

+156
-12
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ go 1.24.4
44

55
require (
66
github.com/mark3labs/mcp-go v0.32.0
7-
github.com/reportportal/goRP/v5 v5.1.2
7+
github.com/reportportal/goRP/v5 v5.1.3
88
github.com/stretchr/testify v1.10.0
99
github.com/urfave/cli/v3 v3.3.8
1010
github.com/yosida95/uritemplate/v3 v3.0.2

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ github.com/mark3labs/mcp-go v0.32.0 h1:fgwmbfL2gbd67obg57OfV2Dnrhs1HtSdlY/i5fn7M
1414
github.com/mark3labs/mcp-go v0.32.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4=
1515
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
1616
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
17-
github.com/reportportal/goRP/v5 v5.1.2 h1:scAj806S65P6HCDx/YMiOJtw2mPU0w2LUx0cxeBbEVg=
18-
github.com/reportportal/goRP/v5 v5.1.2/go.mod h1:Au44IuWeL3UBHyve7U64kNDQMtlrYBp8aeiHjgPDrGs=
17+
github.com/reportportal/goRP/v5 v5.1.3 h1:0uLbch6Qwkd2Zcy4RtAVR2HSLlBjtiIX4YgH7wk7ZE0=
18+
github.com/reportportal/goRP/v5 v5.1.3/go.mod h1:d2O3iaBElP/OA9p92xcmIuwmVaXX4HY9lT/YpmL5t6g=
1919
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
2020
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
2121
github.com/spf13/cast v1.8.0 h1:gEN9K4b8Xws4EX0+a0reLmhq8moKn7ntRlQYgjPeCDk=

internal/reportportal/ctx_utils.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package mcpreportportal
2+
3+
import (
4+
"context"
5+
"net/url"
6+
)
7+
8+
type contextKey string
9+
10+
var contextKeyQueryParams = contextKey(
11+
"queryParams",
12+
) // Key for storing query parameters in the context
13+
14+
func WithQueryParams(ctx context.Context, queryParams url.Values) context.Context {
15+
// Create a new context with the query parameters
16+
return context.WithValue(ctx, contextKeyQueryParams, queryParams)
17+
}
18+
19+
func QueryParamsFromContext(ctx context.Context) (url.Values, bool) {
20+
// Retrieve the query parameters from the context
21+
queryParams, ok := ctx.Value(contextKeyQueryParams).(url.Values)
22+
return queryParams, ok
23+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package mcpreportportal
2+
3+
import (
4+
"context"
5+
"net/url"
6+
"testing"
7+
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func TestWithAndQueryParamsFromContext(t *testing.T) {
12+
ctx := context.Background()
13+
params := url.Values{}
14+
params.Add("foo", "bar")
15+
params.Add("baz", "qux")
16+
17+
ctxWithParams := WithQueryParams(ctx, params)
18+
retrieved, ok := QueryParamsFromContext(ctxWithParams)
19+
assert.True(t, ok)
20+
assert.Equal(t, params, retrieved)
21+
22+
// Test with context that does not have query params
23+
_, ok = QueryParamsFromContext(context.Background())
24+
assert.False(t, ok)
25+
}
26+
27+
func TestQueryParamsFromContext_Absent(t *testing.T) {
28+
ctx := context.Background()
29+
val, ok := QueryParamsFromContext(ctx)
30+
assert.False(t, ok)
31+
assert.Nil(t, val)
32+
}
33+
34+
func TestQueryParamsFromContext_PresentNil(t *testing.T) {
35+
ctx := WithQueryParams(context.Background(), nil)
36+
val, ok := QueryParamsFromContext(ctx)
37+
assert.True(t, ok)
38+
assert.Nil(t, val)
39+
}
40+
41+
func TestQueryParamsFromContext_PresentEmpty(t *testing.T) {
42+
params := url.Values{}
43+
ctx := WithQueryParams(context.Background(), params)
44+
val, ok := QueryParamsFromContext(ctx)
45+
assert.True(t, ok)
46+
assert.Equal(t, params, val)
47+
}
48+
49+
func TestWithQueryParams_Overwrite(t *testing.T) {
50+
params1 := url.Values{}
51+
params1.Add("foo", "bar")
52+
ctx := WithQueryParams(context.Background(), params1)
53+
54+
params2 := url.Values{}
55+
params2.Add("baz", "qux")
56+
ctx = WithQueryParams(ctx, params2)
57+
58+
val, ok := QueryParamsFromContext(ctx)
59+
assert.True(t, ok)
60+
assert.Equal(t, params2, val)
61+
assert.NotEqual(t, params1, val)
62+
}

internal/reportportal/items.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7+
"log/slog"
78

89
"github.com/mark3labs/mcp-go/mcp"
910
"github.com/mark3labs/mcp-go/server"
1011
"github.com/reportportal/goRP/v5/pkg/gorp"
1112
"github.com/yosida95/uritemplate/v3"
1213
)
1314

15+
const itemsDefaultSorting = "startTime,DESC" // default sorting order for test items
16+
1417
// TestItemResources is a struct that encapsulates the ReportPortal client.
1518
type TestItemResources struct {
1619
client *gorp.Client // Client to interact with the ReportPortal API
@@ -42,6 +45,7 @@ func (lr *TestItemResources) toolListLaunchTestItems() (tool mcp.Tool, handler s
4245
mcp.Description("Page size"),
4346
),
4447
), func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
48+
slog.Debug("START PROCESSING")
4549
project, err := extractProject(request)
4650
if err != nil {
4751
return mcp.NewToolResultError(err.Error()), nil
@@ -55,14 +59,14 @@ func (lr *TestItemResources) toolListLaunchTestItems() (tool mcp.Tool, handler s
5559
}
5660

5761
// Fetch test items from ReportPortal using the provided page details
58-
items, _, err := lr.client.TestItemAPI.GetTestItemsV2(ctx, project).
62+
items, rs, err := lr.client.TestItemAPI.GetTestItems(ctx, project).
5963
FilterEqLaunchId(int32(launchId)). //nolint:gosec
6064
PagePage(page).
6165
PageSize(pageSize).
62-
PageSort(defaultSorting).
66+
PageSort(itemsDefaultSorting).
6367
Execute()
6468
if err != nil {
65-
return mcp.NewToolResultError(err.Error()), nil
69+
return mcp.NewToolResultError(extractResponseError(err, rs)), nil
6670
}
6771

6872
// Serialize the launches into JSON format

internal/reportportal/launches.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ import (
1616
)
1717

1818
const (
19-
firstPage = 1 // Default starting page for pagination
20-
singleResult = 1 // Default number of results per page
21-
defaultPageSize = 20 // Default number of items per page
22-
defaultSorting = "startTime,number,DESC" // default sorting order for API requests
19+
firstPage = 1 // Default starting page for pagination
20+
singleResult = 1 // Default number of results per page
21+
defaultPageSize = 20 // Default number of items per page
22+
launchesDefaultSorting = "startTime,number,DESC" // default sorting order for launches
2323
)
2424

2525
// LaunchResources is a struct that encapsulates the ReportPortal client.
@@ -60,7 +60,7 @@ func (lr *LaunchResources) toolListLaunches() (tool mcp.Tool, handler server.Too
6060
launches, _, err := lr.client.LaunchAPI.GetProjectLaunches(ctx, project).
6161
PagePage(page).
6262
PageSize(pageSize).
63-
PageSort(defaultSorting).
63+
PageSort(launchesDefaultSorting).
6464
Execute()
6565
if err != nil {
6666
return mcp.NewToolResultError(err.Error()), nil
@@ -140,7 +140,7 @@ func (lr *LaunchResources) toolGetLastLaunchByName() (mcp.Tool, server.ToolHandl
140140
FilterEqName(launchName).
141141
PagePage(firstPage).
142142
PageSize(singleResult).
143-
PageSort(defaultSorting).
143+
PageSort(launchesDefaultSorting).
144144
Execute()
145145
if err != nil {
146146
return mcp.NewToolResultError(err.Error()), nil
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package mcpreportportal
2+
3+
import "net/http"
4+
5+
func QueryParamsMiddleware(rq *http.Request) {
6+
paramsFromContext, ok := QueryParamsFromContext(rq.Context())
7+
if ok && paramsFromContext != nil {
8+
// If query parameters are present in the context, add them to the request URL
9+
query := rq.URL.Query()
10+
for key, values := range paramsFromContext {
11+
for _, value := range values {
12+
query.Add(key, value)
13+
}
14+
}
15+
rq.URL.RawQuery = query.Encode() // Encode the updated query parameters into the request URL
16+
}
17+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package mcpreportportal
2+
3+
import (
4+
"net/http"
5+
"net/url"
6+
"testing"
7+
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func TestQueryParamsMiddleware(t *testing.T) {
12+
params := url.Values{}
13+
params.Add("a", "1")
14+
params.Add("b", "2")
15+
params.Add("b", "3")
16+
17+
req, _ := http.NewRequest("GET", "http://example.com/path?x=9", nil)
18+
ctx := WithQueryParams(req.Context(), params)
19+
req = req.WithContext(ctx)
20+
21+
QueryParamsMiddleware(req)
22+
23+
got := req.URL.Query()
24+
assert.Equal(t, []string{"9"}, got["x"])
25+
assert.Equal(t, []string{"1"}, got["a"])
26+
assert.Equal(t, []string{"2", "3"}, got["b"])
27+
}

internal/reportportal/server.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ func NewServer(
3232

3333
// Create a new ReportPortal client
3434
rpClient := gorp.NewClient(hostUrl, token)
35+
rpClient.APIClient.GetConfig().Middleware = QueryParamsMiddleware
3536

3637
launches := NewLaunchResources(rpClient, defaultProject)
3738
s.AddTool(launches.toolListLaunches())

internal/reportportal/utils.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package mcpreportportal
22

33
import (
4+
"io"
45
"math"
6+
"net/http"
57

68
"github.com/mark3labs/mcp-go/mcp"
79
)
@@ -34,3 +36,11 @@ func extractPaging(request mcp.CallToolRequest) (int32, int32) {
3436
//nolint:gosec // the int32 is confirmed
3537
return int32(page), int32(pageSize)
3638
}
39+
40+
func extractResponseError(err error, rs *http.Response) string {
41+
errText := err.Error()
42+
if errContent, rErr := io.ReadAll(rs.Body); rErr == nil {
43+
errText = errText + ": " + string(errContent)
44+
}
45+
return errText
46+
}

0 commit comments

Comments
 (0)