218 lines
8.8 KiB
C#
218 lines
8.8 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using MCPForUnity.Editor.Helpers;
|
|
using MCPForUnity.Editor.Services;
|
|
using Newtonsoft.Json.Linq;
|
|
using UnityEditor.TestTools.TestRunner.Api;
|
|
|
|
namespace MCPForUnity.Editor.Resources.Tests
|
|
{
|
|
/// <summary>
|
|
/// Provides access to Unity tests from the Test Framework with pagination and filtering support.
|
|
/// This is a read-only resource that can be queried by MCP clients.
|
|
///
|
|
/// Parameters:
|
|
/// - mode (optional): Filter by "EditMode" or "PlayMode"
|
|
/// - filter (optional): Filter test names by pattern (case-insensitive contains)
|
|
/// - page_size (optional): Number of tests per page (default: 50, max: 200)
|
|
/// - cursor (optional): 0-based cursor for pagination
|
|
/// - page_number (optional): 1-based page number (converted to cursor)
|
|
/// </summary>
|
|
[McpForUnityResource("get_tests")]
|
|
public static class GetTests
|
|
{
|
|
private const int DEFAULT_PAGE_SIZE = 50;
|
|
private const int MAX_PAGE_SIZE = 200;
|
|
|
|
public static async Task<object> HandleCommand(JObject @params)
|
|
{
|
|
// Parse mode filter
|
|
TestMode? modeFilter = null;
|
|
string modeStr = @params?["mode"]?.ToString();
|
|
if (!string.IsNullOrEmpty(modeStr))
|
|
{
|
|
if (!ModeParser.TryParse(modeStr, out modeFilter, out var parseError))
|
|
{
|
|
return new ErrorResponse(parseError);
|
|
}
|
|
}
|
|
|
|
// Parse name filter
|
|
string nameFilter = @params?["filter"]?.ToString();
|
|
|
|
McpLog.Info($"[GetTests] Retrieving tests (mode={modeFilter?.ToString() ?? "all"}, filter={nameFilter ?? "none"})");
|
|
|
|
IReadOnlyList<Dictionary<string, string>> allTests;
|
|
try
|
|
{
|
|
allTests = await MCPServiceLocator.Tests.GetTestsAsync(modeFilter).ConfigureAwait(true);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
McpLog.Error($"[GetTests] Error retrieving tests: {ex.Message}\n{ex.StackTrace}");
|
|
return new ErrorResponse("Failed to retrieve tests");
|
|
}
|
|
|
|
// Apply name filter if provided and convert to List for pagination
|
|
List<Dictionary<string, string>> filteredTests;
|
|
if (!string.IsNullOrEmpty(nameFilter))
|
|
{
|
|
filteredTests = allTests
|
|
.Where(t =>
|
|
(t.ContainsKey("name") && t["name"].IndexOf(nameFilter, StringComparison.OrdinalIgnoreCase) >= 0) ||
|
|
(t.ContainsKey("full_name") && t["full_name"].IndexOf(nameFilter, StringComparison.OrdinalIgnoreCase) >= 0)
|
|
)
|
|
.ToList();
|
|
}
|
|
else
|
|
{
|
|
filteredTests = allTests.ToList();
|
|
}
|
|
|
|
// Clamp page_size before parsing pagination to ensure cursor is computed correctly
|
|
int requestedPageSize = ParamCoercion.CoerceInt(
|
|
@params?["page_size"] ?? @params?["pageSize"],
|
|
DEFAULT_PAGE_SIZE
|
|
);
|
|
int clampedPageSize = System.Math.Min(requestedPageSize, MAX_PAGE_SIZE);
|
|
if (clampedPageSize <= 0) clampedPageSize = DEFAULT_PAGE_SIZE;
|
|
|
|
// Create modified params with clamped page_size for cursor calculation
|
|
var paginationParams = new JObject(@params);
|
|
paginationParams["page_size"] = clampedPageSize;
|
|
|
|
// Parse pagination with clamped page size
|
|
var pagination = PaginationRequest.FromParams(paginationParams, DEFAULT_PAGE_SIZE);
|
|
|
|
// Create paginated response
|
|
var response = PaginationResponse<Dictionary<string, string>>.Create(filteredTests, pagination);
|
|
|
|
string message = !string.IsNullOrEmpty(nameFilter)
|
|
? $"Retrieved {response.Items.Count} of {response.TotalCount} tests matching '{nameFilter}' (cursor {response.Cursor})"
|
|
: $"Retrieved {response.Items.Count} of {response.TotalCount} tests (cursor {response.Cursor})";
|
|
|
|
return new SuccessResponse(message, response);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// DEPRECATED: Use get_tests with mode parameter instead.
|
|
/// Provides access to Unity tests for a specific mode (EditMode or PlayMode).
|
|
/// This is a read-only resource that can be queried by MCP clients.
|
|
///
|
|
/// Parameters:
|
|
/// - mode (required): "EditMode" or "PlayMode"
|
|
/// - filter (optional): Filter test names by pattern (case-insensitive contains)
|
|
/// - page_size (optional): Number of tests per page (default: 50, max: 200)
|
|
/// - cursor (optional): 0-based cursor for pagination
|
|
/// </summary>
|
|
[McpForUnityResource("get_tests_for_mode")]
|
|
public static class GetTestsForMode
|
|
{
|
|
private const int DEFAULT_PAGE_SIZE = 50;
|
|
private const int MAX_PAGE_SIZE = 200;
|
|
|
|
public static async Task<object> HandleCommand(JObject @params)
|
|
{
|
|
string modeStr = @params?["mode"]?.ToString();
|
|
if (string.IsNullOrEmpty(modeStr))
|
|
{
|
|
return new ErrorResponse("'mode' parameter is required");
|
|
}
|
|
|
|
if (!ModeParser.TryParse(modeStr, out var parsedMode, out var parseError))
|
|
{
|
|
return new ErrorResponse(parseError);
|
|
}
|
|
|
|
// Parse name filter
|
|
string nameFilter = @params?["filter"]?.ToString();
|
|
|
|
McpLog.Info($"[GetTestsForMode] Retrieving tests for mode: {parsedMode.Value} (filter={nameFilter ?? "none"})");
|
|
|
|
IReadOnlyList<Dictionary<string, string>> allTests;
|
|
try
|
|
{
|
|
allTests = await MCPServiceLocator.Tests.GetTestsAsync(parsedMode).ConfigureAwait(true);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
McpLog.Error($"[GetTestsForMode] Error retrieving tests: {ex.Message}\n{ex.StackTrace}");
|
|
return new ErrorResponse("Failed to retrieve tests");
|
|
}
|
|
|
|
// Apply name filter if provided and convert to List for pagination
|
|
List<Dictionary<string, string>> filteredTests;
|
|
if (!string.IsNullOrEmpty(nameFilter))
|
|
{
|
|
filteredTests = allTests
|
|
.Where(t =>
|
|
(t.ContainsKey("name") && t["name"].IndexOf(nameFilter, StringComparison.OrdinalIgnoreCase) >= 0) ||
|
|
(t.ContainsKey("full_name") && t["full_name"].IndexOf(nameFilter, StringComparison.OrdinalIgnoreCase) >= 0)
|
|
)
|
|
.ToList();
|
|
}
|
|
else
|
|
{
|
|
filteredTests = allTests.ToList();
|
|
}
|
|
|
|
// Clamp page_size before parsing pagination to ensure cursor is computed correctly
|
|
int requestedPageSize = ParamCoercion.CoerceInt(
|
|
@params?["page_size"] ?? @params?["pageSize"],
|
|
DEFAULT_PAGE_SIZE
|
|
);
|
|
int clampedPageSize = System.Math.Min(requestedPageSize, MAX_PAGE_SIZE);
|
|
if (clampedPageSize <= 0) clampedPageSize = DEFAULT_PAGE_SIZE;
|
|
|
|
// Create modified params with clamped page_size for cursor calculation
|
|
var paginationParams = new JObject(@params);
|
|
paginationParams["page_size"] = clampedPageSize;
|
|
|
|
// Parse pagination with clamped page size
|
|
var pagination = PaginationRequest.FromParams(paginationParams, DEFAULT_PAGE_SIZE);
|
|
|
|
// Create paginated response
|
|
var response = PaginationResponse<Dictionary<string, string>>.Create(filteredTests, pagination);
|
|
|
|
string message = nameFilter != null
|
|
? $"Retrieved {response.Items.Count} of {response.TotalCount} {parsedMode.Value} tests matching '{nameFilter}'"
|
|
: $"Retrieved {response.Items.Count} of {response.TotalCount} {parsedMode.Value} tests";
|
|
|
|
return new SuccessResponse(message, response);
|
|
}
|
|
}
|
|
|
|
internal static class ModeParser
|
|
{
|
|
internal static bool TryParse(string modeStr, out TestMode? mode, out string error)
|
|
{
|
|
error = null;
|
|
mode = null;
|
|
|
|
if (string.IsNullOrWhiteSpace(modeStr))
|
|
{
|
|
error = "'mode' parameter cannot be empty";
|
|
return false;
|
|
}
|
|
|
|
if (modeStr.Equals("EditMode", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
mode = TestMode.EditMode;
|
|
return true;
|
|
}
|
|
|
|
if (modeStr.Equals("PlayMode", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
mode = TestMode.PlayMode;
|
|
return true;
|
|
}
|
|
|
|
error = $"Unknown test mode: '{modeStr}'. Use 'EditMode' or 'PlayMode'";
|
|
return false;
|
|
}
|
|
}
|
|
}
|