diff --git a/docs/mstest-runner-protocol/001-protocol-intro.md b/docs/mstest-runner-protocol/001-protocol-intro.md index 79ac23cf63..04b4078afa 100644 --- a/docs/mstest-runner-protocol/001-protocol-intro.md +++ b/docs/mstest-runner-protocol/001-protocol-intro.md @@ -225,6 +225,13 @@ interface InitializeParams { // If true, the client supports the testing/testUpdates/attachments request. attachmentsSupport: true, + // If true, the client is stateful: it persists an addressable set of test nodes for the + // whole session and keeps each node in its last-known state until it is explicitly updated + // (for example, an IDE test explorer). If false or missing, the client is stateless: it + // consumes test updates as a stream and does not retain node state after the run + // (for example, `dotnet test`). Defaults to false. + isStateful: true, + // If true, the client support a port to which child processes // can connect to. // Note: The test runner is expected to ensure the synchronization of messages diff --git a/src/Platform/Microsoft.Testing.Platform/Hosts/ConsoleTestHost.cs b/src/Platform/Microsoft.Testing.Platform/Hosts/ConsoleTestHost.cs index a00b732c24..1d941b4e08 100644 --- a/src/Platform/Microsoft.Testing.Platform/Hosts/ConsoleTestHost.cs +++ b/src/Platform/Microsoft.Testing.Platform/Hosts/ConsoleTestHost.cs @@ -24,7 +24,7 @@ internal sealed class ConsoleTestHost( : CommonHost(serviceProvider) { private static readonly ClientInfo ClientInfoHost = new("testingplatform-console", PlatformVersion.Version); - private static readonly IClientInfo ClientInfoService = new ClientInfoService("testingplatform-console", PlatformVersion.Version); + private static readonly IClientInfo ClientInfoService = new ClientInfoService("testingplatform-console", PlatformVersion.Version, new ClientCapabilitiesService(IsStateful: false)); private readonly ILogger _logger = serviceProvider.GetLoggerFactory().CreateLogger(); private readonly IClock _clock = serviceProvider.GetClock(); diff --git a/src/Platform/Microsoft.Testing.Platform/Hosts/ServerTestHost.RequestExecution.cs b/src/Platform/Microsoft.Testing.Platform/Hosts/ServerTestHost.RequestExecution.cs index 98fc2d95b7..b2d20f8ad7 100644 --- a/src/Platform/Microsoft.Testing.Platform/Hosts/ServerTestHost.RequestExecution.cs +++ b/src/Platform/Microsoft.Testing.Platform/Hosts/ServerTestHost.RequestExecution.cs @@ -38,7 +38,7 @@ private async Task HandleRequestCoreAsync(RequestMessage message, RpcInv case (JsonRpcMethods.Initialize, InitializeRequestArgs args): _client = new(args.ClientInfo.Name, args.ClientInfo.Version); - _clientInfoService = new ClientInfoService(args.ClientInfo.Name, args.ClientInfo.Version); + _clientInfoService = new ClientInfoService(args.ClientInfo.Name, args.ClientInfo.Version, new ClientCapabilitiesService(args.Capabilities.IsStateful)); await _logger.LogDebugAsync($"Connection established with '{_client.Id}', protocol version {_client.Version}").ConfigureAwait(false); INamedFeatureCapability? namedFeatureCapability = ServiceProvider.GetTestFrameworkCapabilities().GetCapability(); diff --git a/src/Platform/Microsoft.Testing.Platform/InternalAPI/InternalAPI.Unshipped.txt b/src/Platform/Microsoft.Testing.Platform/InternalAPI/InternalAPI.Unshipped.txt index c84445ece8..7676911637 100644 --- a/src/Platform/Microsoft.Testing.Platform/InternalAPI/InternalAPI.Unshipped.txt +++ b/src/Platform/Microsoft.Testing.Platform/InternalAPI/InternalAPI.Unshipped.txt @@ -1,3 +1,28 @@ #nullable enable +const Microsoft.Testing.Platform.ServerMode.JsonRpcStrings.IsStateful = "isStateful" -> string! +Microsoft.Testing.Platform.ServerMode.ClientCapabilities.ClientCapabilities(bool DebuggerProvider, bool IsStateful) -> void +Microsoft.Testing.Platform.ServerMode.ClientCapabilities.Deconstruct(out bool DebuggerProvider, out bool IsStateful) -> void +Microsoft.Testing.Platform.ServerMode.ClientCapabilities.IsStateful.get -> bool +Microsoft.Testing.Platform.ServerMode.ClientCapabilities.IsStateful.init -> void +Microsoft.Testing.Platform.Services.ClientCapabilitiesService +Microsoft.Testing.Platform.Services.ClientCapabilitiesService.$() -> Microsoft.Testing.Platform.Services.ClientCapabilitiesService! +Microsoft.Testing.Platform.Services.ClientCapabilitiesService.ClientCapabilitiesService(bool IsStateful) -> void +Microsoft.Testing.Platform.Services.ClientCapabilitiesService.Deconstruct(out bool IsStateful) -> void +Microsoft.Testing.Platform.Services.ClientCapabilitiesService.Equals(Microsoft.Testing.Platform.Services.ClientCapabilitiesService? other) -> bool +Microsoft.Testing.Platform.Services.ClientCapabilitiesService.IsStateful.get -> bool +Microsoft.Testing.Platform.Services.ClientCapabilitiesService.IsStateful.init -> void +Microsoft.Testing.Platform.Services.ClientInfoService.Capabilities.get -> Microsoft.Testing.Platform.Services.IClientCapabilities! +Microsoft.Testing.Platform.Services.ClientInfoService.Capabilities.init -> void +Microsoft.Testing.Platform.Services.ClientInfoService.ClientInfoService(string! Id, string! Version, Microsoft.Testing.Platform.Services.IClientCapabilities! Capabilities) -> void +Microsoft.Testing.Platform.Services.ClientInfoService.Deconstruct(out string! Id, out string! Version, out Microsoft.Testing.Platform.Services.IClientCapabilities! Capabilities) -> void +override Microsoft.Testing.Platform.Services.ClientCapabilitiesService.Equals(object? obj) -> bool +override Microsoft.Testing.Platform.Services.ClientCapabilitiesService.GetHashCode() -> int +override Microsoft.Testing.Platform.Services.ClientCapabilitiesService.ToString() -> string! +static Microsoft.Testing.Platform.Services.ClientCapabilitiesService.operator !=(Microsoft.Testing.Platform.Services.ClientCapabilitiesService? left, Microsoft.Testing.Platform.Services.ClientCapabilitiesService? right) -> bool +static Microsoft.Testing.Platform.Services.ClientCapabilitiesService.operator ==(Microsoft.Testing.Platform.Services.ClientCapabilitiesService? left, Microsoft.Testing.Platform.Services.ClientCapabilitiesService? right) -> bool +*REMOVED*Microsoft.Testing.Platform.ServerMode.ClientCapabilities.ClientCapabilities(bool DebuggerProvider) -> void +*REMOVED*Microsoft.Testing.Platform.ServerMode.ClientCapabilities.Deconstruct(out bool DebuggerProvider) -> void +*REMOVED*Microsoft.Testing.Platform.Services.ClientInfoService.ClientInfoService(string! Id, string! Version) -> void +*REMOVED*Microsoft.Testing.Platform.Services.ClientInfoService.Deconstruct(out string! Id, out string! Version) -> void static Microsoft.Testing.Platform.IPC.Serializers.BaseSerializer.ReadFields(System.IO.Stream! stream, System.Func! tryReadField) -> void static Microsoft.Testing.Platform.IPC.Serializers.BaseSerializer.WriteListPayload(System.IO.Stream! stream, ushort fieldId, T[]? list, System.Action! writeItem) -> void diff --git a/src/Platform/Microsoft.Testing.Platform/PublicAPI/PublicAPI.Unshipped.txt b/src/Platform/Microsoft.Testing.Platform/PublicAPI/PublicAPI.Unshipped.txt index 7dc5c58110..01eaf70577 100644 --- a/src/Platform/Microsoft.Testing.Platform/PublicAPI/PublicAPI.Unshipped.txt +++ b/src/Platform/Microsoft.Testing.Platform/PublicAPI/PublicAPI.Unshipped.txt @@ -1 +1,4 @@ #nullable enable +[TPEXP]Microsoft.Testing.Platform.Services.IClientCapabilities +[TPEXP]Microsoft.Testing.Platform.Services.IClientCapabilities.IsStateful.get -> bool +[TPEXP]Microsoft.Testing.Platform.Services.IClientInfo.Capabilities.get -> Microsoft.Testing.Platform.Services.IClientCapabilities! diff --git a/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs b/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs index 55e3f182d1..291a57a731 100644 --- a/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs +++ b/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/Json/Json.Deserializers.cs @@ -128,8 +128,13 @@ private static void RegisterDefaultDeserializers(Dictionary(testing, JsonRpcStrings.DebuggerProvider)); + DebuggerProvider: json.Bind(testing, JsonRpcStrings.DebuggerProvider), + IsStateful: isStateful); }); deserializers[typeof(InitializeResponseArgs)] = new JsonElementDeserializer( diff --git a/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/JsonRpcMethods.cs b/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/JsonRpcMethods.cs index 579f21a080..12501e55f9 100644 --- a/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/JsonRpcMethods.cs +++ b/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/JsonRpcMethods.cs @@ -40,6 +40,7 @@ internal static class JsonRpcStrings public const string Capabilities = "capabilities"; public const string Testing = "testing"; public const string DebuggerProvider = "debuggerProvider"; + public const string IsStateful = "isStateful"; public const string SupportsDiscovery = "supportsDiscovery"; public const string MultiRequestSupport = "experimental_multiRequestSupport"; public const string VSTestProviderSupport = "vstestProvider"; diff --git a/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/RpcMessages.cs b/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/RpcMessages.cs index 62d6e161a9..b6057a5112 100644 --- a/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/RpcMessages.cs +++ b/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/RpcMessages.cs @@ -90,7 +90,7 @@ internal sealed record InvalidRequestParamsArgs(int ErrorCode, string ErrorMessa internal sealed record ClientInfo(string Name, string Version); -internal sealed record ClientCapabilities(bool DebuggerProvider); +internal sealed record ClientCapabilities(bool DebuggerProvider, bool IsStateful); internal sealed record ServerInfo(string Name, string Version); diff --git a/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/SerializerUtilities.Deserializers.cs b/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/SerializerUtilities.Deserializers.cs index 86bc97a4f8..3bcfb8d72a 100644 --- a/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/SerializerUtilities.Deserializers.cs +++ b/src/Platform/Microsoft.Testing.Platform/ServerMode/JsonRpc/SerializerUtilities.Deserializers.cs @@ -110,8 +110,9 @@ private static void RegisterDeserializers() IDictionary capabilities = GetRequiredPropertyFromJson>(properties, JsonRpcStrings.Capabilities); IDictionary testingCapabilities = GetRequiredPropertyFromJson>(capabilities, JsonRpcStrings.Testing); bool debuggerProvider = GetRequiredPropertyFromJson(testingCapabilities, JsonRpcStrings.DebuggerProvider); + bool isStateful = GetOptionalPropertyFromJson(testingCapabilities, JsonRpcStrings.IsStateful) as bool? ?? false; - return new ClientCapabilities(debuggerProvider); + return new ClientCapabilities(debuggerProvider, isStateful); }); Deserializers[typeof(InitializeResponseArgs)] = new ObjectDeserializer(properties => diff --git a/src/Platform/Microsoft.Testing.Platform/Services/ClientCapabilitiesService.cs b/src/Platform/Microsoft.Testing.Platform/Services/ClientCapabilitiesService.cs new file mode 100644 index 0000000000..9222518814 --- /dev/null +++ b/src/Platform/Microsoft.Testing.Platform/Services/ClientCapabilitiesService.cs @@ -0,0 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.Testing.Platform.Services; + +internal sealed record ClientCapabilitiesService(bool IsStateful) : IClientCapabilities; diff --git a/src/Platform/Microsoft.Testing.Platform/Services/ClientInfoService.cs b/src/Platform/Microsoft.Testing.Platform/Services/ClientInfoService.cs index 410c1e45e4..5a92a243e4 100644 --- a/src/Platform/Microsoft.Testing.Platform/Services/ClientInfoService.cs +++ b/src/Platform/Microsoft.Testing.Platform/Services/ClientInfoService.cs @@ -3,4 +3,4 @@ namespace Microsoft.Testing.Platform.Services; -internal sealed record ClientInfoService(string Id, string Version) : IClientInfo; +internal sealed record ClientInfoService(string Id, string Version, IClientCapabilities Capabilities) : IClientInfo; diff --git a/src/Platform/Microsoft.Testing.Platform/Services/IClientCapabilities.cs b/src/Platform/Microsoft.Testing.Platform/Services/IClientCapabilities.cs new file mode 100644 index 0000000000..a90604471b --- /dev/null +++ b/src/Platform/Microsoft.Testing.Platform/Services/IClientCapabilities.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.Testing.Platform.Services; + +/// +/// Represents the capabilities declared by the client that is driving the test host. +/// +/// +/// Capabilities are opt-in: unless a client explicitly declares a capability, the platform assumes the +/// most conservative (default) behavior. This lets a test framework tailor its behavior to how the client +/// intends to consume the results without having to guess based on the environment or transport. +/// +[Experimental("TPEXP", UrlFormat = "https://aka.ms/testingplatform/diagnostics#{0}")] +public interface IClientCapabilities +{ + /// + /// Gets a value indicating whether the client is stateful. + /// + /// + /// A stateful client persists an addressable set of test nodes for the whole session and keeps each node in + /// its last-known state until it is explicitly updated (for example, an IDE test explorer). A stateless client + /// consumes updates as a stream and does not retain node state after the run (for example, dotnet test). + /// The default is (stateless); a client opts into stateful behavior. + /// + bool IsStateful { get; } +} diff --git a/src/Platform/Microsoft.Testing.Platform/Services/IClientInfo.cs b/src/Platform/Microsoft.Testing.Platform/Services/IClientInfo.cs index 56361588a2..ff4f76a30e 100644 --- a/src/Platform/Microsoft.Testing.Platform/Services/IClientInfo.cs +++ b/src/Platform/Microsoft.Testing.Platform/Services/IClientInfo.cs @@ -18,4 +18,9 @@ public interface IClientInfo /// Gets the client version. /// string Version { get; } + + /// + /// Gets the capabilities declared by the client. + /// + IClientCapabilities Capabilities { get; } } diff --git a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/ServerMode/v1.0.0/ClientCapabilities.cs b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/ServerMode/v1.0.0/ClientCapabilities.cs index 11073acf3d..c463045a3b 100644 --- a/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/ServerMode/v1.0.0/ClientCapabilities.cs +++ b/test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/ServerMode/v1.0.0/ClientCapabilities.cs @@ -11,4 +11,7 @@ public sealed record ClientCapabilities( public sealed record ClientTestingCapabilities( [property: JsonProperty("debuggerProvider")] - bool DebuggerProvider); + bool DebuggerProvider, + + [property: JsonProperty("isStateful")] + bool IsStateful = false); diff --git a/test/UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests/ObjectModel/ObjectModelConvertersTests.cs b/test/UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests/ObjectModel/ObjectModelConvertersTests.cs index e184daedb4..92596d053b 100644 --- a/test/UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests/ObjectModel/ObjectModelConvertersTests.cs +++ b/test/UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests/ObjectModel/ObjectModelConvertersTests.cs @@ -18,7 +18,7 @@ namespace Microsoft.Testing.Extensions.VSTestBridge.UnitTests.ObjectModel; [TestClass] public sealed class ObjectModelConvertersTests { - private static readonly IClientInfo ClientInfo = new ClientInfoService(WellKnownClients.VisualStudio, "1.0.0"); + private static readonly IClientInfo ClientInfo = new ClientInfoService(WellKnownClients.VisualStudio, "1.0.0", new ClientCapabilitiesService(IsStateful: false)); [TestMethod] [DataRow(true)] diff --git a/test/UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests/ObjectModel/RunSettingsPatcherTests.cs b/test/UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests/ObjectModel/RunSettingsPatcherTests.cs index 262f7bc9ae..b49abfd080 100644 --- a/test/UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests/ObjectModel/RunSettingsPatcherTests.cs +++ b/test/UnitTests/Microsoft.Testing.Extensions.VSTestBridge.UnitTests/ObjectModel/RunSettingsPatcherTests.cs @@ -22,7 +22,7 @@ public void Patch_WhenNoRunSettingsProvided_CreateRunSettingsWithResultsDirector { _configuration.Setup(x => x[PlatformConfigurationConstants.PlatformResultDirectory]).Returns("/PlatformResultDirectory"); XDocument runSettingsDocument = RunSettingsPatcher.Patch(null, _configuration.Object, - new ClientInfoService(string.Empty, string.Empty), _commandLineOptions.Object); + new ClientInfoService(string.Empty, string.Empty, new ClientCapabilitiesService(IsStateful: false)), _commandLineOptions.Object); Assert.AreEqual( "/PlatformResultDirectory", runSettingsDocument.XPathSelectElement("RunSettings/RunConfiguration/ResultsDirectory")!.Value); @@ -41,7 +41,7 @@ public void Patch_WithRunSettingsProvidedButMissingResultsDirectory_AddsElement( _configuration.Setup(x => x[PlatformConfigurationConstants.PlatformResultDirectory]).Returns("/PlatformResultDirectory"); - XDocument runSettingsDocument = RunSettingsPatcher.Patch(runSettings, _configuration.Object, new ClientInfoService(string.Empty, string.Empty), _commandLineOptions.Object); + XDocument runSettingsDocument = RunSettingsPatcher.Patch(runSettings, _configuration.Object, new ClientInfoService(string.Empty, string.Empty, new ClientCapabilitiesService(IsStateful: false)), _commandLineOptions.Object); Assert.AreEqual( "/PlatformResultDirectory", runSettingsDocument.XPathSelectElement("RunSettings/RunConfiguration/ResultsDirectory")!.Value); @@ -62,7 +62,7 @@ public void Patch_WithRunSettingsContainingResultsDirectory_EntryIsNotOverridden """; _configuration.Setup(x => x[PlatformConfigurationConstants.PlatformResultDirectory]).Returns("/PlatformResultDirectory"); - XDocument runSettingsDocument = RunSettingsPatcher.Patch(runSettings, _configuration.Object, new ClientInfoService(string.Empty, string.Empty), _commandLineOptions.Object); + XDocument runSettingsDocument = RunSettingsPatcher.Patch(runSettings, _configuration.Object, new ClientInfoService(string.Empty, string.Empty, new ClientCapabilitiesService(IsStateful: false)), _commandLineOptions.Object); Assert.AreEqual( "/PlatformResultDirectoryFromFile", runSettingsDocument.XPathSelectElement("RunSettings/RunConfiguration/ResultsDirectory")!.Value); @@ -91,7 +91,7 @@ public void Patch_WhenRunSettingsExists_MergesParameters() }); _configuration.Setup(x => x[PlatformConfigurationConstants.PlatformResultDirectory]).Returns("/PlatformResultDirectory"); - XDocument runSettingsDocument = RunSettingsPatcher.Patch(runSettings, _configuration.Object, new ClientInfoService(string.Empty, string.Empty), + XDocument runSettingsDocument = RunSettingsPatcher.Patch(runSettings, _configuration.Object, new ClientInfoService(string.Empty, string.Empty, new ClientCapabilitiesService(IsStateful: false)), _commandLineOptions.Object); XElement[] testRunParameters = [.. runSettingsDocument.XPathSelectElements("RunSettings/TestRunParameters/Parameter")]; @@ -115,7 +115,7 @@ public void Patch_WhenRunSettingsDoesNotExist_AddParameters() }); _configuration.Setup(x => x[PlatformConfigurationConstants.PlatformResultDirectory]).Returns("/PlatformResultDirectory"); - XDocument runSettingsDocument = RunSettingsPatcher.Patch(null, _configuration.Object, new ClientInfoService(string.Empty, string.Empty), + XDocument runSettingsDocument = RunSettingsPatcher.Patch(null, _configuration.Object, new ClientInfoService(string.Empty, string.Empty, new ClientCapabilitiesService(IsStateful: false)), _commandLineOptions.Object); XElement[] testRunParameters = [.. runSettingsDocument.XPathSelectElements("RunSettings/TestRunParameters/Parameter")]; diff --git a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/ServerMode/JsonTests.cs b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/ServerMode/JsonTests.cs index 9cb9e43d32..3b0452597d 100644 --- a/test/UnitTests/Microsoft.Testing.Platform.UnitTests/ServerMode/JsonTests.cs +++ b/test/UnitTests/Microsoft.Testing.Platform.UnitTests/ServerMode/JsonTests.cs @@ -202,6 +202,91 @@ public void GetProperties_WhenPropertiesDelegateIsNotSet_ThrowsInvalidOperationE Assert.Contains(nameof(TestJsonObjectSerializer), exception.Message); } + [TestMethod] + public void Deserialize_InitializeRequest_WithIsStatefulTrue_StjPath_SurfacesStatefulClient() + { + // Arrange + Json json = new(); + const string initializeParams = """ + { + "processId": 1, + "clientInfo": { "name": "client", "version": "1.0.0" }, + "capabilities": { "testing": { "debuggerProvider": true, "isStateful": true } } + } + """; + + // Act + InitializeRequestArgs args = json.Deserialize(initializeParams.AsMemory()); + + // Assert + Assert.IsTrue(args.Capabilities.IsStateful); + } + + [TestMethod] + public void Deserialize_InitializeRequest_WithoutIsStateful_StjPath_DefaultsToStateless() + { + // Arrange + Json json = new(); + const string initializeParams = """ + { + "processId": 1, + "clientInfo": { "name": "client", "version": "1.0.0" }, + "capabilities": { "testing": { "debuggerProvider": true } } + } + """; + + // Act + InitializeRequestArgs args = json.Deserialize(initializeParams.AsMemory()); + + // Assert + Assert.IsFalse(args.Capabilities.IsStateful); + } + + [TestMethod] + public void Deserialize_ClientCapabilities_WithIsStatefulTrue_JsonitePath_SurfacesStatefulClient() + { + // Arrange + Dictionary properties = new() + { + ["capabilities"] = new Dictionary + { + ["testing"] = new Dictionary + { + ["debuggerProvider"] = true, + ["isStateful"] = true, + }, + }, + }; + + // Act + ClientCapabilities capabilities = SerializerUtilities.Deserialize(properties); + + // Assert + Assert.IsTrue(capabilities.IsStateful); + } + + [TestMethod] + public void Deserialize_ClientCapabilities_WithoutIsStateful_JsonitePath_DefaultsToStateless() + { + // Arrange + Dictionary properties = new() + { + ["capabilities"] = new Dictionary + { + ["testing"] = new Dictionary + { + ["debuggerProvider"] = true, + }, + }, + }; + + // Act + ClientCapabilities capabilities = SerializerUtilities.Deserialize(properties); + + // Assert + Assert.IsFalse(capabilities.IsStateful); + } + private sealed class TestJsonObjectSerializer : JsonObjectSerializer; private sealed class Person