Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions internal/mcp_handlers/tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ func RegisterTools(
registerTool(s, tools.getLastReportTool)
registerTool(s, tools.getReportByIdTool)
registerTool(s, tools.getSelectorByParametersTool)
registerTool(s, tools.getAllSelectorsTool)
registerTool(s, tools.updateHealingResultTool)
}

Expand Down Expand Up @@ -246,6 +247,38 @@ func (ti *McpTool) getSelectorByParametersTool() (*mcp.Tool, ToolHandler[map[str
}
}

func (ti *McpTool) getAllSelectorsTool() (*mcp.Tool, ToolHandler[any, any]) {
return &mcp.Tool{
Name: "getAllSelectors",
Description: "Retrieves all selectors stored in Healenium. Returns a list of all selector records including their locators, class names, method names, commands, and URLs.",
},
func(ctx context.Context, req *mcp.CallToolRequest, args any) (*mcp.CallToolResult, any, error) {
// Create a new HTTP GET request.
resp, err := ti.client.R().SetContext(ctx).Get("/healenium/selector/all")
if err != nil {
return nil, nil, fmt.Errorf(
"failed to fetch all selectors from /healenium/selector/all: %w",
err,
)
}

// Check HTTP status code
if !resp.IsSuccess() {
return nil, nil, fmt.Errorf(
"failed to fetch all selectors: received status code %d, body: %s",
resp.StatusCode(),
resp.String(),
)
}

return &mcp.CallToolResult{
Content: []mcp.Content{
&mcp.TextContent{Text: resp.String()},
},
}, nil, nil
}
}

func (ti *McpTool) updateHealingResultTool() (*mcp.Tool, ToolHandler[map[string]any, any]) {
return &mcp.Tool{
Name: "updateHealingResult",
Expand Down
207 changes: 207 additions & 0 deletions internal/mcp_handlers/tools_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,37 @@ func contains(s, substr string) bool {
return strings.Contains(s, substr)
}

// createHealeniumMockServerForAllSelectors creates a mock HTTP server that simulates Healenium backend for /healenium/selector/all
func createHealeniumMockServerForAllSelectors(
t *testing.T,
response string,
statusCode int,
) *httptest.Server {
t.Helper()

return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Verify the request is to the correct endpoint
if r.URL.Path != "/healenium/selector/all" {
t.Errorf("expected path /healenium/selector/all but got %s", r.URL.Path)
w.WriteHeader(http.StatusNotFound)
return
}

// Verify it's a GET request
if r.Method != http.MethodGet {
t.Errorf("expected GET method but got %s", r.Method)
w.WriteHeader(http.StatusMethodNotAllowed)
return
}

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(statusCode)
if _, err := w.Write([]byte(response)); err != nil {
t.Errorf("failed to write response: %v", err)
}
}))
}

// TestGetReportByIdTool_Integration tests the complete flow of getReportByIdTool
func TestGetReportByIdTool_Integration(t *testing.T) {
tests := []struct {
Expand Down Expand Up @@ -1251,3 +1282,179 @@ func createMockUpdateHealingResultServer(
w.WriteHeader(statusCode)
}))
}

func TestGetAllSelectorsTool_Integration(t *testing.T) {
tests := []struct {
name string
healeniumResponse string
healeniumStatusCode int
expectedSuccess bool
expectedErrorContent string
}{
{
name: "successful fetch all selectors",
healeniumResponse: `[
{
"id": "f9b3131fa4c55466f4cacfb15ff545f2",
"type": null,
"nodePath": null,
"elementIds": null,
"sessionId": null,
"enableHealing": true,
"urlKey": false,
"locator": "By.cssSelector(textarea:enabled)",
"className": "com.epam.healenium.tests.CssTest",
"methodName": "testCssEnabled",
"command": "Single Locator",
"url": "https://elenastepuro.github.io/test_env/index.html"
},
{
"id": "b155f909d4e5f359ab3545eb49b26d82",
"type": null,
"nodePath": null,
"elementIds": null,
"sessionId": null,
"enableHealing": true,
"urlKey": false,
"locator": "By.tagName(test_tag)",
"className": "com.epam.healenium.tests.SemanticTest",
"methodName": "testFindElementByTagName",
"command": "Single Locator",
"url": "https://elenastepuro.github.io/test_env/index.html"
},
{
"id": "05db8fd2ee1b1a0c69465efb15c8f408",
"type": null,
"nodePath": null,
"elementIds": null,
"sessionId": null,
"enableHealing": true,
"urlKey": false,
"locator": "By.cssSelector(#change_id)",
"className": "com.epam.healenium.tests.CssTest",
"methodName": "testCssId",
"command": "Single Locator",
"url": "https://elenastepuro.github.io/test_env/index.html"
}
]`,
healeniumStatusCode: http.StatusOK,
expectedSuccess: true,
},
{
name: "empty selectors list",
healeniumResponse: `[]`,
healeniumStatusCode: http.StatusOK,
expectedSuccess: true,
},
{
name: "healenium server returns 404",
healeniumResponse: `{"error": "Endpoint not found"}`,
healeniumStatusCode: http.StatusNotFound,
expectedSuccess: false,
expectedErrorContent: "failed to fetch all selectors: received status code 404",
},
{
name: "healenium server returns 500",
healeniumResponse: `{"error": "Internal server error"}`,
healeniumStatusCode: http.StatusInternalServerError,
expectedSuccess: false,
expectedErrorContent: "failed to fetch all selectors: received status code 500",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Step 1: Create mock Healenium server (backend that provides selectors)
healeniumServerMock := createHealeniumMockServerForAllSelectors(
t,
tt.healeniumResponse,
tt.healeniumStatusCode,
)
defer healeniumServerMock.Close()

// Step 2: Create Healenium MCP Server pointing to the mock backend
healeniumMcpServer := createHealeniumMCPServer(t, healeniumServerMock.URL)

// Step 3: Call the tool handler directly
_, handler := healeniumMcpServer.getAllSelectorsTool()
ctx := context.Background()
req := &mcp.CallToolRequest{}

result, _, err := handler(ctx, req, nil)

// Step 4: Verify results
if tt.expectedSuccess {
if err != nil {
t.Fatalf("expected success but got error: %v", err)
}
if result == nil {
t.Fatal("expected result but got nil")
}

// Verify the response content
if len(result.Content) == 0 {
t.Fatal("expected content but got empty array")
}

textContent, ok := result.Content[0].(*mcp.TextContent)
if !ok {
t.Fatalf("expected TextContent but got %T", result.Content[0])
}

if textContent.Text != tt.healeniumResponse {
t.Errorf(
"expected response %q but got %q",
tt.healeniumResponse,
textContent.Text,
)
}
} else {
if err == nil {
t.Fatal("expected error but got nil")
}
// Verify error message contains expected content
if !contains(err.Error(), tt.expectedErrorContent) {
t.Errorf("expected error to contain %q but got %q", tt.expectedErrorContent, err.Error())
}
}
})
}
}

func TestGetAllSelectorsTool_Timeout(t *testing.T) {
// Create a mock Healenium server that delays response
healeniumServerMock := httptest.NewServer(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Delay response to trigger timeout
time.Sleep(2 * time.Second)
w.WriteHeader(http.StatusOK)
if _, err := w.Write([]byte(`[]`)); err != nil {
t.Errorf("failed to write response: %v", err)
}
}),
)
defer healeniumServerMock.Close()

// Create MCP server with short timeout
healeniumURL, _ := url.Parse(healeniumServerMock.URL)
client := resty.New().
SetBaseURL(healeniumURL.String()).
SetTimeout(500 * time.Millisecond) // Short timeout

mcpTool := newMcpTool(client, nil)
_, handler := mcpTool.getAllSelectorsTool()

// Call the tool
ctx := context.Background()
req := &mcp.CallToolRequest{}

_, _, err := handler(ctx, req, nil)
if err == nil {
t.Fatal("expected timeout error but got nil")
}

// Verify it's a timeout-related error
if !contains(err.Error(), "failed to fetch all selectors") {
t.Errorf("expected timeout error but got: %v", err)
}
}
Loading