diff --git a/Unity-MCP-Plugin/Assets/root/Editor/Scripts/API/Tool/TestRunner.Run.cs b/Unity-MCP-Plugin/Assets/root/Editor/Scripts/API/Tool/TestRunner.Run.cs index f4456793..a067cba0 100644 --- a/Unity-MCP-Plugin/Assets/root/Editor/Scripts/API/Tool/TestRunner.Run.cs +++ b/Unity-MCP-Plugin/Assets/root/Editor/Scripts/API/Tool/TestRunner.Run.cs @@ -34,7 +34,7 @@ public static partial class Tool_TestRunner )] [Description(@"Execute Unity tests and return detailed results. Supports filtering by test mode, assembly, namespace, class, and method. Be default recommended to use 'EditMode' for faster iteration during development.")] - public static async Task Run + public static async Task> Run ( [Description("Test mode to run. Options: '" + nameof(TestMode.EditMode) + "', '" + nameof(TestMode.PlayMode) + "'. Default: '" + nameof(TestMode.EditMode) + "'")] TestMode testMode = TestMode.EditMode, @@ -47,7 +47,9 @@ public static async Task Run [Description("Specific fully qualified test method to run (optional). Example: 'MyTestNamespace.FixtureName.TestName'")] string? testMethod = null, - [Description("Include test result messages in the test results (default: true). If just need pass/fail status, set to false.")] + [Description("Include details for all tests, both passing and failing (default: false). If you just need details for failing tests, set to false.")] + bool includePassingTests = false, + [Description("Include test result messages in the test results (default: true). If you just need pass/fail status, set to false.")] bool includeMessages = true, [Description("Include stack traces in the test results (default: false).")] bool includeStacktrace = false, @@ -64,15 +66,17 @@ public static async Task Run ) { if (requestId == null || string.IsNullOrWhiteSpace(requestId)) - return ResponseCallTool.Error("Original request with valid RequestID must be provided."); + return ResponseCallValueTool.Error("Original request with valid RequestID must be provided."); return await MainThread.Instance.RunAsync(async () => { if (UnityMcpPlugin.IsLogEnabled(LogLevel.Info)) Debug.Log($"[TestRunner] ------------------------------------- Preparing to run {testMode} tests."); + try { TestResultCollector.TestCallRequestID.Value = requestId; + TestResultCollector.IncludePassingTests.Value = includePassingTests; TestResultCollector.IncludeMessage.Value = includeMessages; TestResultCollector.IncludeMessageStacktrace.Value = includeStacktrace; @@ -86,17 +90,16 @@ public static async Task Run if (UnityMcpPlugin.IsLogEnabled(LogLevel.Info)) Debug.Log($"[TestRunner] Running {testMode} tests with filters: {filterParams}"); - // Validate specific test mode filter var validation = await ValidateTestFilters(TestRunnerApi, testMode, filterParams); if (validation != null) - return ResponseCallTool.Error(validation).SetRequestID(requestId); + return ResponseCallValueTool.Error(validation).SetRequestID(requestId); var filter = CreateTestFilter(testMode, filterParams); // Delay test running, first need to return response to caller MainThread.Instance.Run(() => TestRunnerApi.Execute(new ExecutionSettings(filter))); - return ResponseCallTool.Processing().SetRequestID(requestId); + return ResponseCallValueTool.Processing().SetRequestID(requestId); } catch (Exception ex) { @@ -105,7 +108,7 @@ public static async Task Run Debug.LogException(ex); Debug.LogError($"[TestRunner] ------------------------------------- Exception {testMode} tests."); } - return ResponseCallTool.Error(Error.TestExecutionFailed(ex.Message)).SetRequestID(requestId); + return ResponseCallValueTool.Error(Error.TestExecutionFailed(ex.Message)).SetRequestID(requestId); } }).Unwrap(); } diff --git a/Unity-MCP-Plugin/Assets/root/Editor/Scripts/API/Tool/TestRunner.cs b/Unity-MCP-Plugin/Assets/root/Editor/Scripts/API/Tool/TestRunner.cs index 4ce01479..d304eeeb 100644 --- a/Unity-MCP-Plugin/Assets/root/Editor/Scripts/API/Tool/TestRunner.cs +++ b/Unity-MCP-Plugin/Assets/root/Editor/Scripts/API/Tool/TestRunner.cs @@ -12,6 +12,7 @@ using System.Collections.Generic; using com.IvanMurzak.McpPlugin; using com.IvanMurzak.Unity.MCP.Editor.API.TestRunner; +using com.IvanMurzak.Unity.MCP.Runtime.Utils; using UnityEditor; using UnityEditor.TestTools.TestRunner.Api; using UnityEngine; @@ -25,6 +26,8 @@ public static partial class Tool_TestRunner static readonly object _lock = new(); static volatile TestRunnerApi? _testRunnerApi = null!; static volatile TestResultCollector? _resultCollector = null!; + static volatile bool _callbacksRegistered = false; + static Tool_TestRunner() { _testRunnerApi ??= CreateInstance(); @@ -44,12 +47,28 @@ public static TestRunnerApi TestRunnerApi } public static TestRunnerApi CreateInstance() { - // if (UnityMcpPlugin.IsLogActive(MCP.Utils.LogLevel.Trace)) - // Debug.Log($"[{nameof(TestRunnerApi)}] Ctor."); + if (UnityMcpPlugin.IsLogEnabled(LogLevel.Trace)) + Debug.Log($"[{nameof(TestRunnerApi)}] Creating new instance. Existing API: {_testRunnerApi != null}, Existing Collector: {_resultCollector != null}, Callbacks Registered: {_callbacksRegistered}"); _resultCollector ??= new TestResultCollector(); var testRunnerApi = ScriptableObject.CreateInstance(); - testRunnerApi.RegisterCallbacks(_resultCollector); + + // Only register callbacks once globally to prevent accumulation + // Unity's TestRunnerApi maintains a static callback list, so multiple RegisterCallbacks calls add duplicates + if (!_callbacksRegistered) + { + if (UnityMcpPlugin.IsLogEnabled(LogLevel.Trace)) + Debug.Log($"[{nameof(TestRunnerApi)}] Registering callbacks for the first (and only) time."); + + testRunnerApi.RegisterCallbacks(_resultCollector); + _callbacksRegistered = true; + } + else + { + if (UnityMcpPlugin.IsLogEnabled(LogLevel.Trace)) + Debug.LogWarning($"[{nameof(TestRunnerApi)}] Callbacks already registered globally - skipping registration."); + } + return testRunnerApi; } diff --git a/Unity-MCP-Plugin/Assets/root/Editor/Scripts/API/Tool/TestRunner/TestLogEntry.cs b/Unity-MCP-Plugin/Assets/root/Editor/Scripts/API/Tool/TestRunner/TestLogEntry.cs index 0022106d..98bd09ef 100644 --- a/Unity-MCP-Plugin/Assets/root/Editor/Scripts/API/Tool/TestRunner/TestLogEntry.cs +++ b/Unity-MCP-Plugin/Assets/root/Editor/Scripts/API/Tool/TestRunner/TestLogEntry.cs @@ -16,10 +16,10 @@ namespace com.IvanMurzak.Unity.MCP.Editor.API.TestRunner { public class TestLogEntry { - public string Condition; - public string? StackTrace; - public LogType Type; - public DateTime Timestamp; + public string Condition { get; set; } + public string? StackTrace { get; set; } + public LogType Type { get; set; } + public DateTime Timestamp { get; set; } public int LogLevel => ToLogLevel(Type); diff --git a/Unity-MCP-Plugin/Assets/root/Editor/Scripts/API/Tool/TestRunner/TestResultCollector.cs b/Unity-MCP-Plugin/Assets/root/Editor/Scripts/API/Tool/TestRunner/TestResultCollector.cs index 1d5e3697..6bd74fa2 100644 --- a/Unity-MCP-Plugin/Assets/root/Editor/Scripts/API/Tool/TestRunner/TestResultCollector.cs +++ b/Unity-MCP-Plugin/Assets/root/Editor/Scripts/API/Tool/TestRunner/TestResultCollector.cs @@ -23,7 +23,7 @@ namespace com.IvanMurzak.Unity.MCP.Editor.API.TestRunner { public class TestResultCollector : ICallbacks { - static int counter = 0; + static volatile int counter = 0; readonly object _logsMutex = new(); readonly List _results = new(); @@ -53,6 +53,7 @@ public List GetLogs() public static PlayerPrefsString TestCallRequestID = new PlayerPrefsString("Unity_MCP_TestRunner_TestCallRequestID"); + public static PlayerPrefsBool IncludePassingTests = new PlayerPrefsBool("Unity_MCP_TestRunner_IncludePassingTests"); public static PlayerPrefsBool IncludeMessage = new PlayerPrefsBool("Unity_MCP_TestRunner_IncludeMessage", true); public static PlayerPrefsBool IncludeMessageStacktrace = new PlayerPrefsBool("Unity_MCP_TestRunner_IncludeStacktrace"); @@ -85,10 +86,7 @@ public void RunStarted(ITestAdaptor testsToRun) _summary.Clear(); _summary.TotalTests = testCount; - // Subscribe on log messages - Application.logMessageReceived -= OnLogMessageReceived; - Application.logMessageReceived += OnLogMessageReceived; - + // Subscribe to log messages (using threaded version to catch logs from all threads) Application.logMessageReceivedThreaded -= OnLogMessageReceived; Application.logMessageReceivedThreaded += OnLogMessageReceived; @@ -101,7 +99,6 @@ public void RunFinished(ITestResultAdaptor result) UnityMcpPlugin.Instance.LogInfo("RunFinished", typeof(TestResultCollector)); // Unsubscribe from log messages - Application.logMessageReceived -= OnLogMessageReceived; Application.logMessageReceivedThreaded -= OnLogMessageReceived; var duration = DateTime.Now - startTime; @@ -134,12 +131,22 @@ public void RunFinished(ITestResultAdaptor result) TestCallRequestID.Value = string.Empty; if (string.IsNullOrEmpty(requestId) == false) { - var response = ResponseCallTool - .Success(FormatTestResults( - includeMessage: IncludeMessage.Value, - includeLogs: IncludeLogs.Value, - includeMessageStacktrace: IncludeMessageStacktrace.Value, - includeLogsStacktrace: IncludeLogsStacktrace.Value)) + var structuredResponse = CreateStructuredResponse( + includePassingTests: IncludePassingTests.Value, + includeMessage: IncludeMessage.Value, + includeLogs: IncludeLogs.Value, + includeMessageStacktrace: IncludeMessageStacktrace.Value, + includeLogsStacktrace: IncludeLogsStacktrace.Value); + + var mcpPlugin = UnityMcpPlugin.Instance.McpPluginInstance ?? throw new InvalidOperationException("MCP Plugin instance is not available."); + var jsonOptions = mcpPlugin.McpManager.Reflector.JsonSerializerOptions; + var jsonNode = System.Text.Json.JsonSerializer.SerializeToNode(structuredResponse, jsonOptions); + var jsonString = jsonNode?.ToJsonString(); + + var response = ResponseCallValueTool + .SuccessStructured( + structuredContent: jsonNode, + message: jsonString ?? "[Success] Test execution completed.") // Needed for MCP backward compatibility: https://modelcontextprotocol.io/specification/2025-06-18/server/tools#structured-content .SetRequestID(requestId); _ = UnityMcpPlugin.NotifyToolRequestCompleted(new RequestToolCompletedData @@ -163,7 +170,7 @@ public void TestFinished(ITestResultAdaptor result) var testResult = new TestResultData { Name = result.Test.FullName, - Status = result.TestStatus.ToString(), + Status = ConvertTestStatus(result.TestStatus), Duration = TimeSpan.FromSeconds(result.Duration), Message = result.Message, StackTrace = result.StackTrace @@ -217,67 +224,49 @@ void OnLogMessageReceived(string condition, string stackTrace, LogType type) } } - string FormatTestResults(bool includeMessage, bool includeMessageStacktrace, bool includeLogs, bool includeLogsStacktrace) + TestRunResponse CreateStructuredResponse(bool includePassingTests, bool includeMessage, bool includeMessageStacktrace, bool includeLogs, bool includeLogsStacktrace) { var results = GetResults(); var summary = GetSummary(); var logs = GetLogs(); - var output = new StringBuilder(); - output.AppendLine("[Success] Test execution completed."); - output.AppendLine(); - - // Summary - output.AppendLine("=== TEST SUMMARY ==="); - output.AppendLine($"Status: {summary.Status}"); - output.AppendLine($"Total: {summary.TotalTests}"); - output.AppendLine($"Passed: {summary.PassedTests}"); - output.AppendLine($"Failed: {summary.FailedTests}"); - output.AppendLine($"Skipped: {summary.SkippedTests}"); - output.AppendLine($"Duration: {summary.Duration:hh\\:mm\\:ss\\.fff}"); - output.AppendLine(); - - // Individual test results - if (results.Any()) + var response = new TestRunResponse + { + Summary = summary, + Results = new List() + }; + + // Filter test results based on includePassingTests, includeMessage and includeMessageStacktrace + foreach (var result in results) { - output.AppendLine("=== TEST RESULTS ==="); - foreach (var result in results) + // Skip passing tests if includePassingTests is false + if (!includePassingTests && result.Status == TestResultStatus.Passed) + continue; + + var filteredResult = new TestResultData { - output.AppendLine($"[{result.Status}] {result.Name}"); - output.AppendLine($" Duration: {result.Duration:ss\\.fff}s"); - - if (includeMessage) - { - if (!string.IsNullOrEmpty(result.Message)) - output.AppendLine($" Message: {result.Message}"); - } - - if (includeMessageStacktrace) - { - if (!string.IsNullOrEmpty(result.StackTrace)) - output.AppendLine($" Stack Trace: {result.StackTrace}"); - } - - output.AppendLine(); - } + Name = result.Name, + Status = result.Status, + Duration = result.Duration, + Message = includeMessage ? result.Message : null, + StackTrace = includeMessageStacktrace ? result.StackTrace : null + }; + response.Results.Add(filteredResult); } - // Console logs + // Include logs if requested if (includeLogs && logs.Any()) { var minLogLevel = TestLogEntry.ToLogLevel((LogType)IncludeLogsMinLevel.Value); - output.AppendLine("=== CONSOLE LOGS ==="); - foreach (var log in logs) - { - if (log.LogLevel < minLogLevel) - continue; - output.AppendLine(log.ToStringFormat( - includeType: true, - includeStacktrace: includeLogsStacktrace)); - } + response.Logs = logs + .Where(log => log.LogLevel >= minLogLevel) + .Select(log => includeLogsStacktrace + ? log + : new TestLogEntry(log.Type, log.Condition, null, log.Timestamp)) + .ToList(); } - return output.ToString(); + return response; } public static int CountTests(ITestAdaptor test) @@ -297,5 +286,16 @@ public static int CountTests(ITestAdaptor test) return 0; } } + + static TestResultStatus ConvertTestStatus(TestStatus testStatus) + { + return testStatus switch + { + TestStatus.Passed => TestResultStatus.Passed, + TestStatus.Failed => TestResultStatus.Failed, + TestStatus.Skipped => TestResultStatus.Skipped, + _ => TestResultStatus.Skipped + }; + } } } diff --git a/Unity-MCP-Plugin/Assets/root/Editor/Scripts/API/Tool/TestRunner/TestResultData.cs b/Unity-MCP-Plugin/Assets/root/Editor/Scripts/API/Tool/TestRunner/TestResultData.cs index 87eb6906..c5815018 100644 --- a/Unity-MCP-Plugin/Assets/root/Editor/Scripts/API/Tool/TestRunner/TestResultData.cs +++ b/Unity-MCP-Plugin/Assets/root/Editor/Scripts/API/Tool/TestRunner/TestResultData.cs @@ -16,7 +16,7 @@ namespace com.IvanMurzak.Unity.MCP.Editor.API.TestRunner public class TestResultData { public string Name { get; set; } = string.Empty; - public string Status { get; set; } = string.Empty; + public TestResultStatus Status { get; set; } = TestResultStatus.Skipped; public TimeSpan Duration { get; set; } public string? Message { get; set; } public string? StackTrace { get; set; } diff --git a/Unity-MCP-Plugin/Assets/root/Editor/Scripts/API/Tool/TestRunner/TestResultStatus.cs b/Unity-MCP-Plugin/Assets/root/Editor/Scripts/API/Tool/TestRunner/TestResultStatus.cs new file mode 100644 index 00000000..070544ba --- /dev/null +++ b/Unity-MCP-Plugin/Assets/root/Editor/Scripts/API/Tool/TestRunner/TestResultStatus.cs @@ -0,0 +1,20 @@ +/* +┌──────────────────────────────────────────────────────────────────┐ +│ Author: Ivan Murzak (https://github.com/IvanMurzak) │ +│ Repository: GitHub (https://github.com/IvanMurzak/Unity-MCP) │ +│ Copyright (c) 2025 Ivan Murzak │ +│ Licensed under the Apache License, Version 2.0. │ +│ See the LICENSE file in the project root for more information. │ +└──────────────────────────────────────────────────────────────────┘ +*/ +#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. + +namespace com.IvanMurzak.Unity.MCP.Editor.API.TestRunner +{ + public enum TestResultStatus + { + Passed, + Failed, + Skipped + } +} diff --git a/Unity-MCP-Plugin/Assets/root/Editor/Scripts/API/Tool/TestRunner/TestResultStatus.cs.meta b/Unity-MCP-Plugin/Assets/root/Editor/Scripts/API/Tool/TestRunner/TestResultStatus.cs.meta new file mode 100644 index 00000000..a5a209ea --- /dev/null +++ b/Unity-MCP-Plugin/Assets/root/Editor/Scripts/API/Tool/TestRunner/TestResultStatus.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2226a1d18a1b76c478ecb26d8815e4f3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unity-MCP-Plugin/Assets/root/Editor/Scripts/API/Tool/TestRunner/TestRunResponse.cs b/Unity-MCP-Plugin/Assets/root/Editor/Scripts/API/Tool/TestRunner/TestRunResponse.cs new file mode 100644 index 00000000..66f602e9 --- /dev/null +++ b/Unity-MCP-Plugin/Assets/root/Editor/Scripts/API/Tool/TestRunner/TestRunResponse.cs @@ -0,0 +1,22 @@ +/* +┌──────────────────────────────────────────────────────────────────┐ +│ Author: Ivan Murzak (https://github.com/IvanMurzak) │ +│ Repository: GitHub (https://github.com/IvanMurzak/Unity-MCP) │ +│ Copyright (c) 2025 Ivan Murzak │ +│ Licensed under the Apache License, Version 2.0. │ +│ See the LICENSE file in the project root for more information. │ +└──────────────────────────────────────────────────────────────────┘ +*/ + +#nullable enable +using System.Collections.Generic; + +namespace com.IvanMurzak.Unity.MCP.Editor.API.TestRunner +{ + public class TestRunResponse + { + public TestSummaryData Summary { get; set; } = new TestSummaryData(); + public List Results { get; set; } = new List(); + public List? Logs { get; set; } + } +} diff --git a/Unity-MCP-Plugin/Assets/root/Editor/Scripts/API/Tool/TestRunner/TestRunResponse.cs.meta b/Unity-MCP-Plugin/Assets/root/Editor/Scripts/API/Tool/TestRunner/TestRunResponse.cs.meta new file mode 100644 index 00000000..87757abc --- /dev/null +++ b/Unity-MCP-Plugin/Assets/root/Editor/Scripts/API/Tool/TestRunner/TestRunResponse.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b73658fef863958479617144b3ac8b41 \ No newline at end of file diff --git a/Unity-MCP-Plugin/Assets/root/Plugins/com.IvanMurzak.McpPlugin/netstandard2.1/McpPlugin.Common.dll b/Unity-MCP-Plugin/Assets/root/Plugins/com.IvanMurzak.McpPlugin/netstandard2.1/McpPlugin.Common.dll index 60ac1ca5..96c438af 100644 Binary files a/Unity-MCP-Plugin/Assets/root/Plugins/com.IvanMurzak.McpPlugin/netstandard2.1/McpPlugin.Common.dll and b/Unity-MCP-Plugin/Assets/root/Plugins/com.IvanMurzak.McpPlugin/netstandard2.1/McpPlugin.Common.dll differ diff --git a/Unity-MCP-Plugin/Assets/root/Plugins/com.IvanMurzak.McpPlugin/netstandard2.1/McpPlugin.dll b/Unity-MCP-Plugin/Assets/root/Plugins/com.IvanMurzak.McpPlugin/netstandard2.1/McpPlugin.dll index 4a3edb12..7699d701 100644 Binary files a/Unity-MCP-Plugin/Assets/root/Plugins/com.IvanMurzak.McpPlugin/netstandard2.1/McpPlugin.dll and b/Unity-MCP-Plugin/Assets/root/Plugins/com.IvanMurzak.McpPlugin/netstandard2.1/McpPlugin.dll differ diff --git a/Unity-MCP-Plugin/Assets/root/Plugins/com.IvanMurzak.ReflectorNet/ReflectorNet.dll b/Unity-MCP-Plugin/Assets/root/Plugins/com.IvanMurzak.ReflectorNet/ReflectorNet.dll index aeaf58f7..90042ba6 100644 Binary files a/Unity-MCP-Plugin/Assets/root/Plugins/com.IvanMurzak.ReflectorNet/ReflectorNet.dll and b/Unity-MCP-Plugin/Assets/root/Plugins/com.IvanMurzak.ReflectorNet/ReflectorNet.dll differ diff --git a/Unity-MCP-Plugin/Assets/root/Tests/Editor/Tool/RunToolStructuredContentTests.cs b/Unity-MCP-Plugin/Assets/root/Tests/Editor/Tool/RunToolStructuredContentTests.cs index a5bfb2a0..1eb136cc 100644 --- a/Unity-MCP-Plugin/Assets/root/Tests/Editor/Tool/RunToolStructuredContentTests.cs +++ b/Unity-MCP-Plugin/Assets/root/Tests/Editor/Tool/RunToolStructuredContentTests.cs @@ -17,6 +17,7 @@ using com.IvanMurzak.McpPlugin; using com.IvanMurzak.McpPlugin.Common.Model; using com.IvanMurzak.ReflectorNet; +using com.IvanMurzak.ReflectorNet.Utils; using com.IvanMurzak.Unity.MCP.Utils; using Microsoft.Extensions.Logging; using NUnit.Framework; @@ -65,7 +66,7 @@ public IEnumerator RunTool_ReturnsInt_ShouldReturnAsStringWithoutStructuredConte Assert.IsNotNull(result, "Result should not be null"); Assert.AreEqual(ResponseStatus.Success, result.Status, "Should return success status"); Assert.AreEqual(expectedValue.ToString(), result.GetMessage(), "Should return int as string"); - Assert.IsNull(result.StructuredContent, "Primitive types should not have structured content"); + Assert.IsNull(result.StructuredContent![JsonSchema.Result], "Primitive types should not have structured content"); // Verify the int value can be parsed back to the original value var parsedValue = int.Parse(result.GetMessage()); @@ -91,7 +92,7 @@ public IEnumerator RunTool_ReturnsString_ShouldReturnAsStringWithoutStructuredCo Assert.IsNotNull(result, "Result should not be null"); Assert.AreEqual(ResponseStatus.Success, result.Status, "Should return success status"); Assert.AreEqual(expectedValue, result.GetMessage(), "Should return string value"); - Assert.IsNull(result.StructuredContent, "String is primitive and should not have structured content"); + Assert.IsNull(result.StructuredContent![JsonSchema.Result], "String is primitive and should not have structured content"); // Verify the string value matches exactly (no corruption) Assert.AreEqual(expectedValue, result.GetMessage(), "String should match original value exactly"); @@ -116,7 +117,7 @@ public IEnumerator RunTool_ReturnsFloat_ShouldReturnAsStringWithoutStructuredCon Assert.IsNotNull(result, "Result should not be null"); Assert.AreEqual(ResponseStatus.Success, result.Status, "Should return success status"); Assert.AreEqual(expectedValue.ToString(), result.GetMessage(), "Should return float as string"); - Assert.IsNull(result.StructuredContent, "Primitive types should not have structured content"); + Assert.IsNull(result.StructuredContent![JsonSchema.Result], "Primitive types should not have structured content"); // Verify the float value can be parsed back to the original value var parsedValue = float.Parse(result.GetMessage()); @@ -142,7 +143,7 @@ public IEnumerator RunTool_ReturnsBool_ShouldReturnAsStringWithoutStructuredCont Assert.IsNotNull(result, "Result should not be null"); Assert.AreEqual(ResponseStatus.Success, result.Status, "Should return success status"); Assert.AreEqual(expectedValue.ToString(), result.GetMessage(), "Should return bool as string"); - Assert.IsNull(result.StructuredContent, "Primitive types should not have structured content"); + Assert.IsNull(result.StructuredContent![JsonSchema.Result], "Primitive types should not have structured content"); // Verify the bool value can be parsed back to the original value var parsedValue = bool.Parse(result.GetMessage()); @@ -168,7 +169,7 @@ public IEnumerator RunTool_ReturnsEnum_ShouldReturnAsStringWithoutStructuredCont Assert.IsNotNull(result, "Result should not be null"); Assert.AreEqual(ResponseStatus.Success, result.Status, "Should return success status"); Assert.AreEqual(expectedEnumValue.ToString(), result.GetMessage(), "Should return enum as string"); - Assert.IsNull(result.StructuredContent, "Enum is primitive and should not have structured content"); + Assert.IsNull(result.StructuredContent![JsonSchema.Result], "Enum is primitive and should not have structured content"); // Verify the enum value can be parsed back to the original value var parsedValue = Enum.Parse(typeof(ResponseStatus), result.GetMessage()); @@ -214,7 +215,7 @@ public IEnumerator RunTool_ReturnsMicrosoftLogLevel_ShouldReturnAsStringWithoutS Assert.IsNotNull(result, "Result should not be null"); Assert.AreEqual(ResponseStatus.Success, result.Status, "Should return success status"); Assert.AreEqual(expectedValue.ToString(), result.GetMessage(), "Should return Microsoft.Extensions.Logging.LogLevel as string"); - Assert.IsNull(result.StructuredContent, "Enum is primitive and should not have structured content"); + Assert.IsNull(result.StructuredContent![JsonSchema.Result], "Enum is primitive and should not have structured content"); // Verify the enum value can be parsed back to the original value var parsedValue = Enum.Parse(typeof(Microsoft.Extensions.Logging.LogLevel), result.GetMessage()); @@ -245,10 +246,10 @@ public IEnumerator RunTool_ReturnsCustomClass_ShouldReturnStructuredContent() Assert.AreEqual(ResponseStatus.Success, result.Status, "Should return success status"); Assert.IsNotNull(result.StructuredContent, "Custom class should have structured content"); - var structuredContent = result.StructuredContent; + var structuredContent = result.StructuredContent![JsonSchema.Result]!; // Check if properties are in camelCase (name, age) or PascalCase (Name, Age) - var nameNode = structuredContent!["name"] ?? structuredContent["Name"]; + var nameNode = structuredContent["name"] ?? structuredContent["Name"]; var ageNode = structuredContent["age"] ?? structuredContent["Age"]; Assert.IsNotNull(nameNode, "Should have name/Name property"); @@ -282,8 +283,8 @@ public IEnumerator RunTool_ReturnsNestedClass_ShouldReturnStructuredContent() Assert.AreEqual(ResponseStatus.Success, result.Status, "Should return success status"); Assert.IsNotNull(result.StructuredContent, "Nested class should have structured content"); - var structuredContent = result.StructuredContent; - var companyNameNode = structuredContent!["companyName"] ?? structuredContent["CompanyName"]; + var structuredContent = result.StructuredContent![JsonSchema.Result]!; + var companyNameNode = structuredContent["companyName"] ?? structuredContent["CompanyName"]; var employeeNode = structuredContent["employee"] ?? structuredContent["Employee"]; Assert.IsNotNull(companyNameNode, "Should have companyName/CompanyName property"); @@ -315,7 +316,7 @@ public IEnumerator RunTool_ReturnsList_ShouldReturnStructuredContent() Assert.AreEqual(ResponseStatus.Success, result.Status, "Should return success status"); Assert.IsNotNull(result.StructuredContent, "List should have structured content"); - var structuredContent = result.StructuredContent!.AsArray(); + var structuredContent = result.StructuredContent![JsonSchema.Result]!.AsArray(); Assert.AreEqual(expectedValue.Count, structuredContent.Count, $"List should have {expectedValue.Count} items"); for (int i = 0; i < expectedValue.Count; i++) { @@ -343,11 +344,11 @@ public IEnumerator RunTool_ReturnsDictionary_ShouldReturnStructuredContent() Assert.AreEqual(ResponseStatus.Success, result.Status, "Should return success status"); Assert.IsNotNull(result.StructuredContent, "Dictionary should have structured content"); - var structuredContent = result.StructuredContent; + var structuredContent = result.StructuredContent![JsonSchema.Result]!; foreach (var kvp in expectedValue) { - Assert.IsNotNull(structuredContent?[kvp.Key], $"Should have {kvp.Key}"); - Assert.AreEqual(kvp.Value, structuredContent?[kvp.Key]?.GetValue(), $"{kvp.Key} value should match"); + Assert.IsNotNull(structuredContent[kvp.Key], $"Should have {kvp.Key}"); + Assert.AreEqual(kvp.Value, structuredContent[kvp.Key]?.GetValue(), $"{kvp.Key} value should match"); } } @@ -371,7 +372,7 @@ public IEnumerator RunTool_ReturnsArray_ShouldReturnStructuredContent() Assert.AreEqual(ResponseStatus.Success, result.Status, "Should return success status"); Assert.IsNotNull(result.StructuredContent, "Array should have structured content"); - var structuredContent = result.StructuredContent!.AsArray(); + var structuredContent = result.StructuredContent![JsonSchema.Result]!.AsArray(); Assert.AreEqual(expectedValue.Length, structuredContent.Count, $"Array should have {expectedValue.Length} items"); for (int i = 0; i < expectedValue.Length; i++) { @@ -403,19 +404,19 @@ public IEnumerator RunTool_ReturnsVector3_ShouldReturnStructuredContent() Assert.AreEqual(ResponseStatus.Success, result.Status, "Should return success status"); Assert.IsNotNull(result.StructuredContent, "Vector3 should have structured content"); - var structuredContent = result.StructuredContent; - Assert.IsNotNull(structuredContent!["x"], "Should have x property"); - Assert.IsNotNull(structuredContent!["y"], "Should have y property"); - Assert.IsNotNull(structuredContent!["z"], "Should have z property"); - Assert.AreEqual(expectedValue.x, structuredContent!["x"]?.GetValue(), 0.001f, "X should match"); - Assert.AreEqual(expectedValue.y, structuredContent!["y"]?.GetValue(), 0.001f, "Y should match"); - Assert.AreEqual(expectedValue.z, structuredContent!["z"]?.GetValue(), 0.001f, "Z should match"); + var structuredContent = result.StructuredContent![JsonSchema.Result]!; + Assert.IsNotNull(structuredContent["x"], "Should have x property"); + Assert.IsNotNull(structuredContent["y"], "Should have y property"); + Assert.IsNotNull(structuredContent["z"], "Should have z property"); + Assert.AreEqual(expectedValue.x, structuredContent["x"]?.GetValue(), 0.001f, "X should match"); + Assert.AreEqual(expectedValue.y, structuredContent["y"]?.GetValue(), 0.001f, "Y should match"); + Assert.AreEqual(expectedValue.z, structuredContent["z"]?.GetValue(), 0.001f, "Z should match"); // Verify the Vector3 can be reconstructed from structured content var reconstructedVector = new Vector3( - structuredContent!["x"]!.GetValue(), - structuredContent!["y"]!.GetValue(), - structuredContent!["z"]!.GetValue() + structuredContent["x"]!.GetValue(), + structuredContent["y"]!.GetValue(), + structuredContent["z"]!.GetValue() ); Assert.AreEqual(expectedValue, reconstructedVector, "Reconstructed Vector3 should match original"); } @@ -440,18 +441,18 @@ public IEnumerator RunTool_ReturnsColor_ShouldReturnStructuredContent() Assert.AreEqual(ResponseStatus.Success, result.Status, "Should return success status"); Assert.IsNotNull(result.StructuredContent, "Color should have structured content"); - var structuredContent = result.StructuredContent; - Assert.IsNotNull(structuredContent!["r"], "Should have r property"); - Assert.IsNotNull(structuredContent!["g"], "Should have g property"); - Assert.IsNotNull(structuredContent!["b"], "Should have b property"); - Assert.IsNotNull(structuredContent!["a"], "Should have a property"); + var structuredContent = result.StructuredContent![JsonSchema.Result]!; + Assert.IsNotNull(structuredContent["r"], "Should have r property"); + Assert.IsNotNull(structuredContent["g"], "Should have g property"); + Assert.IsNotNull(structuredContent["b"], "Should have b property"); + Assert.IsNotNull(structuredContent["a"], "Should have a property"); // Verify the Color can be reconstructed from structured content var reconstructedColor = new Color( - structuredContent!["r"]!.GetValue(), - structuredContent!["g"]!.GetValue(), - structuredContent!["b"]!.GetValue(), - structuredContent!["a"]!.GetValue() + structuredContent["r"]!.GetValue(), + structuredContent["g"]!.GetValue(), + structuredContent["b"]!.GetValue(), + structuredContent["a"]!.GetValue() ); Assert.AreEqual(expectedValue, reconstructedColor, "Reconstructed Color should match original"); } @@ -476,11 +477,11 @@ public IEnumerator RunTool_ReturnsQuaternion_ShouldReturnStructuredContent() Assert.AreEqual(ResponseStatus.Success, result.Status, "Should return success status"); Assert.IsNotNull(result.StructuredContent, "Quaternion should have structured content"); - var structuredContent = result.StructuredContent; - Assert.IsNotNull(structuredContent!["x"], "Should have x property"); - Assert.IsNotNull(structuredContent!["y"], "Should have y property"); - Assert.IsNotNull(structuredContent!["z"], "Should have z property"); - Assert.IsNotNull(structuredContent!["w"], "Should have w property"); + var structuredContent = result.StructuredContent![JsonSchema.Result]!; + Assert.IsNotNull(structuredContent["x"], "Should have x property"); + Assert.IsNotNull(structuredContent["y"], "Should have y property"); + Assert.IsNotNull(structuredContent["z"], "Should have z property"); + Assert.IsNotNull(structuredContent["w"], "Should have w property"); // Verify the Quaternion can be reconstructed from structured content var reconstructedQuaternion = new Quaternion( @@ -540,7 +541,7 @@ public IEnumerator RunTool_ReturnsListOfCustomObjects_ShouldReturnStructuredCont Assert.AreEqual(ResponseStatus.Success, result.Status, "Should return success status"); Assert.IsNotNull(result.StructuredContent, "List of custom objects should have structured content"); - var structuredContent = result.StructuredContent!.AsArray(); + var structuredContent = result.StructuredContent![JsonSchema.Result]!.AsArray(); Assert.AreEqual(expectedValue.Count, structuredContent.Count, $"List should have {expectedValue.Count} items"); for (int i = 0; i < expectedValue.Count; i++) @@ -572,10 +573,10 @@ public IEnumerator RunTool_ReturnsDictionaryWithComplexValues_ShouldReturnStruct Assert.AreEqual(ResponseStatus.Success, result.Status, "Should return success status"); Assert.IsNotNull(result.StructuredContent, "Dictionary with complex values should have structured content"); - var structuredContent = result.StructuredContent; + var structuredContent = result.StructuredContent![JsonSchema.Result]!; foreach (var kvp in expectedValue) { - Assert.IsNotNull(structuredContent![kvp.Key], $"Should have {kvp.Key} key"); + Assert.IsNotNull(structuredContent[kvp.Key], $"Should have {kvp.Key} key"); var nameNode = structuredContent[kvp.Key]?["name"] ?? structuredContent[kvp.Key]?["Name"]; var ageNode = structuredContent[kvp.Key]?["age"] ?? structuredContent[kvp.Key]?["Age"]; @@ -607,7 +608,7 @@ public IEnumerator RunTool_StructuredContent_ShouldBeValidJson() Assert.IsNotNull(result.StructuredContent, "Should have structured content"); // Verify it can be serialized to valid JSON - var jsonString = result.StructuredContent!.ToJsonString(); + var jsonString = result.StructuredContent![JsonSchema.Result]!.ToJsonString(); Assert.IsNotNull(jsonString, "Should serialize to JSON string"); Assert.IsTrue(jsonString.Contains("name") || jsonString.Contains("Name"), "JSON should contain name/Name property"); Assert.IsTrue(jsonString.Contains(expectedValue.Name), $"JSON should contain the name value: {expectedValue.Name}");