diff --git a/CHANGELOG.md b/CHANGELOG.md index a87eb82..520e42a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.9.9] - 2024-07-12 + +- Fix enum deserialization for SendPrimitiveAsync and SendPrimitiveCollectionAsync + ## [1.9.8] - 2024-07-08 - Migrated source of various libraries to mono repository at . diff --git a/Directory.Build.props b/Directory.Build.props index 3b877d4..8f6d44f 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,7 +1,7 @@ - 1.9.8 + 1.9.9 false @@ -10,47 +10,8 @@ latest enable - - - Microsoft - © Microsoft Corporation. All rights reserved. - true - http://go.microsoft.com/fwlink/?LinkID=288890 - https://github.com/microsoft/kiota-dotnet - https://aka.ms/kiota/docs - - https://github.com/microsoft/kiota-dotnet/releases - - true - MIT - README.md - true - true - true - false - false - 35MSSharedLib1024.snk - true - $(NoWarn);NU5048;NETSDK1138 - true - true - true - - false Library - - - - True - - - - - - - - diff --git a/Microsoft.Kiota.sln b/Microsoft.Kiota.sln index fa7d91e..61af83d 100644 --- a/Microsoft.Kiota.sln +++ b/Microsoft.Kiota.sln @@ -44,6 +44,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Kiota.Serializati EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Kiota.Serialization.Text.Tests", "tests\serialization\text\Microsoft.Kiota.Serialization.Text.Tests.csproj", "{5F6AC278-C4A4-4EED-A7D3-1750A4D6FD15}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Kiota.Trimming.Validation", "tests\trimming\Microsoft.Kiota.Trimming.Validation\Microsoft.Kiota.Trimming.Validation.csproj", "{13992912-662D-4A3B-9354-DD49DB1AC2D5}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -110,6 +112,10 @@ Global {5F6AC278-C4A4-4EED-A7D3-1750A4D6FD15}.Debug|Any CPU.Build.0 = Debug|Any CPU {5F6AC278-C4A4-4EED-A7D3-1750A4D6FD15}.Release|Any CPU.ActiveCfg = Release|Any CPU {5F6AC278-C4A4-4EED-A7D3-1750A4D6FD15}.Release|Any CPU.Build.0 = Release|Any CPU + {13992912-662D-4A3B-9354-DD49DB1AC2D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {13992912-662D-4A3B-9354-DD49DB1AC2D5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {13992912-662D-4A3B-9354-DD49DB1AC2D5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {13992912-662D-4A3B-9354-DD49DB1AC2D5}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Directory.Build.props b/src/Directory.Build.props new file mode 100644 index 0000000..91a867f --- /dev/null +++ b/src/Directory.Build.props @@ -0,0 +1,41 @@ + + + + + Microsoft + © Microsoft Corporation. All rights reserved. + true + http://go.microsoft.com/fwlink/?LinkID=288890 + https://github.com/microsoft/kiota-dotnet + https://aka.ms/kiota/docs + + https://github.com/microsoft/kiota-dotnet/releases + + true + MIT + README.md + true + true + true + false + false + 35MSSharedLib1024.snk + true + $(NoWarn);NU5048;NETSDK1138 + true + true + true + + + + + True + + + + + + + + + diff --git a/src/abstractions/Helpers/EnumHelpers.cs b/src/abstractions/Helpers/EnumHelpers.cs index 5f37665..72ffce6 100644 --- a/src/abstractions/Helpers/EnumHelpers.cs +++ b/src/abstractions/Helpers/EnumHelpers.cs @@ -86,46 +86,47 @@ private static ReadOnlySpan ToEnumRawName(ReadOnlySpan span) wher { return null; } - if(type.IsDefined(typeof(FlagsAttribute))) + Type enumType = (Nullable.GetUnderlyingType(type) is { IsEnum: true } underlyingType) ? underlyingType : type; + if(enumType.IsDefined(typeof(FlagsAttribute))) { int intValue = 0; while(rawValue.Length > 0) { int commaIndex = rawValue.IndexOf(','); var valueName = commaIndex < 0 ? rawValue : rawValue.Substring(0, commaIndex); - if(TryGetFieldValueName(type, valueName, out var value)) + if(TryGetFieldValueName(enumType, valueName, out var value)) { valueName = value; } #if NET5_0_OR_GREATER - if(Enum.TryParse(type, valueName, true, out var enumPartResult)) + if(Enum.TryParse(enumType, valueName, true, out var enumPartResult)) intValue |= (int)enumPartResult!; #else try { - intValue |= (int)Enum.Parse(type, valueName, true); + intValue |= (int)Enum.Parse(enumType, valueName, true); } catch { } #endif rawValue = commaIndex < 0 ? string.Empty : rawValue.Substring(commaIndex + 1); } - result = intValue > 0 ? Enum.Parse(type, intValue.ToString(), true) : null; + result = intValue > 0 ? Enum.Parse(enumType, intValue.ToString(), true) : null; } else { - if(TryGetFieldValueName(type, rawValue, out var value)) + if(TryGetFieldValueName(enumType, rawValue, out var value)) { rawValue = value; } #if NET5_0_OR_GREATER - Enum.TryParse(type, rawValue, true, out object? enumResult); + Enum.TryParse(enumType, rawValue, true, out object? enumResult); result = enumResult; #else try { - result = Enum.Parse(type, rawValue, true); + result = Enum.Parse(enumType, rawValue, true); } catch { @@ -134,8 +135,6 @@ private static ReadOnlySpan ToEnumRawName(ReadOnlySpan span) wher #endif } return result; - - } #if NET5_0_OR_GREATER diff --git a/src/abstractions/serialization/IParseNode.cs b/src/abstractions/serialization/IParseNode.cs index 422ef53..ef107f6 100644 --- a/src/abstractions/serialization/IParseNode.cs +++ b/src/abstractions/serialization/IParseNode.cs @@ -95,7 +95,11 @@ public interface IParseNode /// Gets the collection of primitive values of the node. /// /// The collection of primitive values. +#if NET5_0_OR_GREATER + IEnumerable GetCollectionOfPrimitiveValues<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] T>(); +#else IEnumerable GetCollectionOfPrimitiveValues(); +#endif /// /// Gets the collection of enum values of the node. /// diff --git a/src/http/httpClient/HttpClientRequestAdapter.cs b/src/http/httpClient/HttpClientRequestAdapter.cs index af2c2ac..2932ba2 100644 --- a/src/http/httpClient/HttpClientRequestAdapter.cs +++ b/src/http/httpClient/HttpClientRequestAdapter.cs @@ -16,6 +16,7 @@ using Microsoft.Kiota.Abstractions; using Microsoft.Kiota.Abstractions.Authentication; using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions.Helpers; using Microsoft.Kiota.Abstractions.Serialization; using Microsoft.Kiota.Abstractions.Store; using Microsoft.Kiota.Http.HttpClientLibrary.Middleware; @@ -302,6 +303,10 @@ public string? BaseUrl { result = rootNode.GetDateValue(); } + else if(rootNode.GetStringValue() is { Length: > 0 } rawValue) + { + result = EnumHelpers.GetEnumValue(modelType, rawValue); + } else throw new InvalidOperationException("error handling the response, unexpected type"); SetResponseType(result, span); return (ModelType)result!; diff --git a/src/serialization/form/FormParseNode.cs b/src/serialization/form/FormParseNode.cs index 8101a7c..d6cd966 100644 --- a/src/serialization/form/FormParseNode.cs +++ b/src/serialization/form/FormParseNode.cs @@ -5,6 +5,7 @@ using System.Xml; using Microsoft.Kiota.Abstractions; using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions.Helpers; using Microsoft.Kiota.Abstractions.Serialization; #if NET5_0_OR_GREATER using System.Diagnostics.CodeAnalysis; @@ -96,7 +97,11 @@ private static string SanitizeKey(string key) /// Get the collection of primitives of type from the form node /// /// A collection of objects +#if NET5_0_OR_GREATER + public IEnumerable GetCollectionOfPrimitiveValues<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] T>() +#else public IEnumerable GetCollectionOfPrimitiveValues() +#endif { var genericType = typeof(T); var primitiveValueCollection = DecodedValue.Split(ComaSeparator, StringSplitOptions.RemoveEmptyEntries); @@ -226,7 +231,7 @@ private void AssignFieldValues(T item) where T : IParsable #endif { foreach(var v in DecodedValue.Split(ComaSeparator, StringSplitOptions.RemoveEmptyEntries)) - yield return GetEnumValueInternal(v); + yield return EnumHelpers.GetEnumValue(v); } #if NET5_0_OR_GREATER @@ -235,33 +240,6 @@ private void AssignFieldValues(T item) where T : IParsable T? IParseNode.GetEnumValue() #endif { - return GetEnumValueInternal(DecodedValue); - } - - private static T? GetEnumValueInternal(string rawValue) where T : struct, Enum - { - if(string.IsNullOrEmpty(rawValue)) - return null; - if(typeof(T).IsDefined(typeof(FlagsAttribute))) - { - ReadOnlySpan valueSpan = rawValue.AsSpan(); - int value = 0; - while(valueSpan.Length > 0) - { - int commaIndex = valueSpan.IndexOf(','); - ReadOnlySpan valueNameSpan = commaIndex < 0 ? valueSpan : valueSpan.Slice(0, commaIndex); -#if NET6_0_OR_GREATER - if(Enum.TryParse(valueNameSpan, true, out var result)) -#else - if(Enum.TryParse(valueNameSpan.ToString(), true, out var result)) -#endif - value |= (int)(object)result; - valueSpan = commaIndex < 0 ? ReadOnlySpan.Empty : valueSpan.Slice(commaIndex + 1); - } - return (T)(object)value; - } - else if(Enum.TryParse(rawValue, out var result)) - return result; - return null; + return EnumHelpers.GetEnumValue(DecodedValue); } } diff --git a/src/serialization/json/JsonParseNode.cs b/src/serialization/json/JsonParseNode.cs index 2593b83..b7a9d39 100644 --- a/src/serialization/json/JsonParseNode.cs +++ b/src/serialization/json/JsonParseNode.cs @@ -11,6 +11,7 @@ using System.Xml; using Microsoft.Kiota.Abstractions; using Microsoft.Kiota.Abstractions.Extensions; +using Microsoft.Kiota.Abstractions.Helpers; using Microsoft.Kiota.Abstractions.Serialization; #if NET5_0_OR_GREATER @@ -217,29 +218,7 @@ public JsonParseNode(JsonElement node, KiotaJsonSerializationContext jsonSeriali #endif { var rawValue = _jsonNode.GetString(); - if(string.IsNullOrEmpty(rawValue)) return null; - - rawValue = ToEnumRawName(rawValue!); - if(typeof(T).IsDefined(typeof(FlagsAttribute))) - { - ReadOnlySpan valueSpan = rawValue.AsSpan(); - int value = 0; - while(valueSpan.Length > 0) - { - int commaIndex = valueSpan.IndexOf(','); - ReadOnlySpan valueNameSpan = commaIndex < 0 ? valueSpan : valueSpan.Slice(0, commaIndex); -#if NET6_0_OR_GREATER - if(Enum.TryParse(valueNameSpan, true, out var result)) -#else - if(Enum.TryParse(valueNameSpan.ToString(), true, out var result)) -#endif - value |= (int)(object)result; - valueSpan = commaIndex < 0 ? ReadOnlySpan.Empty : valueSpan.Slice(commaIndex + 1); - } - return (T)(object)value; - } - else - return Enum.TryParse(rawValue, true, out var result) ? result : null; + return EnumHelpers.GetEnumValue(rawValue!); } /// @@ -309,7 +288,11 @@ public IEnumerable GetCollectionOfObjectValues(ParsableFactory factory) /// Get the collection of primitives of type from the json node /// /// A collection of objects +#if NET5_0_OR_GREATER + public IEnumerable GetCollectionOfPrimitiveValues<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] T>() +#else public IEnumerable GetCollectionOfPrimitiveValues() +#endif { if(_jsonNode.ValueKind == JsonValueKind.Array) { @@ -347,6 +330,10 @@ public IEnumerable GetCollectionOfPrimitiveValues() yield return (T)(object)currentParseNode.GetDateValue()!; else if(genericType == TypeConstants.TimeType) yield return (T)(object)currentParseNode.GetTimeValue()!; + else if(currentParseNode.GetStringValue() is { Length: > 0 } rawValue) + { + yield return (T)EnumHelpers.GetEnumValue(genericType, rawValue)!; + } else throw new InvalidOperationException($"unknown type for deserialization {genericType.FullName}"); } diff --git a/src/serialization/json/JsonSerializationWriter.cs b/src/serialization/json/JsonSerializationWriter.cs index 27ccfbc..29d2697 100644 --- a/src/serialization/json/JsonSerializationWriter.cs +++ b/src/serialization/json/JsonSerializationWriter.cs @@ -432,7 +432,11 @@ public void WriteAdditionalData(IDictionary value) WriteAnyValue(dataValue.Key, dataValue.Value); } +#if NET5_0_OR_GREATER + private void WriteNonParsableObjectValue<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(string? key,T value) +#else private void WriteNonParsableObjectValue(string? key, T value) +#endif { if(!string.IsNullOrEmpty(key)) writer.WritePropertyName(key!); @@ -440,7 +444,7 @@ private void WriteNonParsableObjectValue(string? key, T value) if(value == null) writer.WriteNullValue(); else - foreach(var oProp in value.GetType().GetProperties()) + foreach(var oProp in typeof(T).GetProperties()) WriteAnyValue(oProp.Name, oProp.GetValue(value)); writer.WriteEndObject(); } diff --git a/src/serialization/text/TextParseNode.cs b/src/serialization/text/TextParseNode.cs index 5f4dda6..4af865d 100644 --- a/src/serialization/text/TextParseNode.cs +++ b/src/serialization/text/TextParseNode.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Xml; using Microsoft.Kiota.Abstractions; +using Microsoft.Kiota.Abstractions.Helpers; using Microsoft.Kiota.Abstractions.Serialization; #if NET5_0_OR_GREATER using System.Diagnostics.CodeAnalysis; @@ -38,7 +39,11 @@ public TextParseNode(string? text) /// public IEnumerable GetCollectionOfObjectValues(ParsableFactory factory) where T : IParsable => throw new InvalidOperationException(NoStructuredDataMessage); /// +#if NET5_0_OR_GREATER + public IEnumerable GetCollectionOfPrimitiveValues<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] T>()=> throw new InvalidOperationException(NoStructuredDataMessage); +#else public IEnumerable GetCollectionOfPrimitiveValues() => throw new InvalidOperationException(NoStructuredDataMessage); +#endif /// public DateTimeOffset? GetDateTimeOffsetValue() => DateTimeOffset.TryParse(Text, out var result) ? result : null; /// @@ -78,5 +83,5 @@ public TextParseNode(string? text) #else public T? GetEnumValue() where T : struct, Enum #endif - => Enum.TryParse(Text, true, out var result) ? result : null; + => Text is null ? null : EnumHelpers.GetEnumValue(Text); } diff --git a/tests/abstractions/EnumHelperTests.cs b/tests/abstractions/EnumHelperTests.cs index 64932ad..d93259e 100644 --- a/tests/abstractions/EnumHelperTests.cs +++ b/tests/abstractions/EnumHelperTests.cs @@ -70,6 +70,14 @@ public void EnumIsParsedIfValueIsInteger() Assert.Equal(TestEnum.First, result); } + [Fact] + public void NullableEnumIsParsedIfValueIsInteger() + { + var result = EnumHelpers.GetEnumValue(typeof(TestEnum?), "0"); + + Assert.Equal(TestEnum.First, result); + } + [Fact] public void EnumWithFlagsIsParsedIfValuesAreIntegers() { @@ -78,6 +86,14 @@ public void EnumWithFlagsIsParsedIfValuesAreIntegers() Assert.Equal(TestEnumWithFlags.Value1 | TestEnumWithFlags.Value2, result); } + [Fact] + public void NullableEnumWithFlagsIsParsedIfValuesAreIntegers() + { + var result = EnumHelpers.GetEnumValue(typeof(TestEnumWithFlags?), "1,2"); + + Assert.Equal(TestEnumWithFlags.Value1 | TestEnumWithFlags.Value2, result); + } + [Fact] public void EnumIsParsedIfValueIsString() { @@ -86,6 +102,14 @@ public void EnumIsParsedIfValueIsString() Assert.Equal(TestEnum.First, result); } + [Fact] + public void NullableEnumIsParsedIfValueIsString() + { + var result = EnumHelpers.GetEnumValue(typeof(TestEnum?), "First"); + + Assert.Equal(TestEnum.First, result); + } + [Fact] public void EnumWithFlagsIsParsedIfValuesAreStrings() { @@ -94,6 +118,14 @@ public void EnumWithFlagsIsParsedIfValuesAreStrings() Assert.Equal(TestEnumWithFlags.Value1 | TestEnumWithFlags.Value3, result); } + [Fact] + public void NullableEnumWithFlagsIsParsedIfValuesAreStrings() + { + var result = EnumHelpers.GetEnumValue(typeof(TestEnumWithFlags?), "Value1,Value3"); + + Assert.Equal(TestEnumWithFlags.Value1 | TestEnumWithFlags.Value3, result); + } + [Fact] public void EnumIsParsedIfValueIsFromEnumMember() { @@ -102,6 +134,14 @@ public void EnumIsParsedIfValueIsFromEnumMember() Assert.Equal(TestEnum.Second, result); } + [Fact] + public void NullableEnumIsParsedIfValueIsFromEnumMember() + { + var result = EnumHelpers.GetEnumValue(typeof(TestEnum?), "Value_2"); + + Assert.Equal(TestEnum.Second, result); + } + [Fact] public void EnumWithFlagsIsParsedIfValuesAreFromEnumMember() { @@ -110,6 +150,14 @@ public void EnumWithFlagsIsParsedIfValuesAreFromEnumMember() Assert.Equal(TestEnumWithFlags.Value2 | TestEnumWithFlags.Value3, result); } + [Fact] + public void NullableEnumWithFlagsIsParsedIfValuesAreFromEnumMember() + { + var result = EnumHelpers.GetEnumValue(typeof(TestEnumWithFlags?), "Value__2,Value__3"); + + Assert.Equal(TestEnumWithFlags.Value2 | TestEnumWithFlags.Value3, result); + } + [Fact] public void IfEnumIsNotParsedThenNullIsReturned() { @@ -117,5 +165,13 @@ public void IfEnumIsNotParsedThenNullIsReturned() Assert.Null(result); } + + [Fact] + public void IfNullableEnumIsNotParsedThenNullIsReturned() + { + var result = EnumHelpers.GetEnumValue(typeof(TestEnum?), "Value_5"); + + Assert.Null(result); + } } } diff --git a/tests/http/httpClient/RequestAdapterTests.cs b/tests/http/httpClient/RequestAdapterTests.cs index 1b8ca2d..a0093f0 100644 --- a/tests/http/httpClient/RequestAdapterTests.cs +++ b/tests/http/httpClient/RequestAdapterTests.cs @@ -3,6 +3,7 @@ using System.IO; using System.Net; using System.Net.Http; +using System.Runtime.Serialization; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -535,5 +536,473 @@ public async Task ThrowsApiExceptionOnMissingMapping(HttpStatusCode statusCode) Assert.Contains("The server returned an unexpected status code and no error factory is registered for this code", apiException.Message); } } + [Fact] + public async Task SendPrimitiveHandleEnumIfValueIsString() + { + var mockHandler = new Mock(); + var client = new HttpClient(mockHandler.Object); + mockHandler.Protected() + .Setup>("SendAsync", ItExpr.IsAny(), ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent("Value1") + }); + + var mockParseNode = new Mock(); + mockParseNode.Setup(x => x.GetStringValue()) + .Returns("Value1"); + + var mockParseNodeFactory = new Mock(); + mockParseNodeFactory.Setup(x => x.GetRootParseNodeAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(Task.FromResult(mockParseNode.Object)); + var adapter = new HttpClientRequestAdapter(_authenticationProvider, mockParseNodeFactory.Object, httpClient: client); + var requestInfo = new RequestInformation + { + HttpMethod = Method.GET, + URI = new Uri("https://example.com") + }; + + var response = await adapter.SendPrimitiveAsync(requestInfo); + + Assert.Equal(TestEnum.Value1, response); + } + + [Fact] + public async Task SendPrimitiveHandleNullableEnumIfValueIsString() + { + var mockHandler = new Mock(); + var client = new HttpClient(mockHandler.Object); + mockHandler.Protected() + .Setup>("SendAsync", ItExpr.IsAny(), ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent("Value1") + }); + + var mockParseNode = new Mock(); + mockParseNode.Setup(x => x.GetStringValue()) + .Returns("Value1"); + + var mockParseNodeFactory = new Mock(); + mockParseNodeFactory.Setup(x => x.GetRootParseNodeAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(Task.FromResult(mockParseNode.Object)); + var adapter = new HttpClientRequestAdapter(_authenticationProvider, mockParseNodeFactory.Object, httpClient: client); + var requestInfo = new RequestInformation + { + HttpMethod = Method.GET, + URI = new Uri("https://example.com") + }; + + var response = await adapter.SendPrimitiveAsync(requestInfo); + + Assert.Equal(TestEnum.Value1, response); + } + + [Fact] + public async Task SendPrimitiveHandleEnumIfValueIsInteger() + { + var mockHandler = new Mock(); + var client = new HttpClient(mockHandler.Object); + mockHandler.Protected() + .Setup>("SendAsync", ItExpr.IsAny(), ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent("1") + }); + + var mockParseNode = new Mock(); + mockParseNode.Setup(x => x.GetStringValue()) + .Returns("1"); + + var mockParseNodeFactory = new Mock(); + mockParseNodeFactory.Setup(x => x.GetRootParseNodeAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(Task.FromResult(mockParseNode.Object)); + var adapter = new HttpClientRequestAdapter(_authenticationProvider, mockParseNodeFactory.Object, httpClient: client); + var requestInfo = new RequestInformation + { + HttpMethod = Method.GET, + URI = new Uri("https://example.com") + }; + + var response = await adapter.SendPrimitiveAsync(requestInfo); + + Assert.Equal(TestEnum.Value2, response); + } + + [Fact] + public async Task SendPrimitiveHandleNullableEnumIfValueIsInteger() + { + var mockHandler = new Mock(); + var client = new HttpClient(mockHandler.Object); + mockHandler.Protected() + .Setup>("SendAsync", ItExpr.IsAny(), ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent("1") + }); + + var mockParseNode = new Mock(); + mockParseNode.Setup(x => x.GetStringValue()) + .Returns("1"); + + var mockParseNodeFactory = new Mock(); + mockParseNodeFactory.Setup(x => x.GetRootParseNodeAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(Task.FromResult(mockParseNode.Object)); + var adapter = new HttpClientRequestAdapter(_authenticationProvider, mockParseNodeFactory.Object, httpClient: client); + var requestInfo = new RequestInformation + { + HttpMethod = Method.GET, + URI = new Uri("https://example.com") + }; + + var response = await adapter.SendPrimitiveAsync(requestInfo); + + Assert.Equal(TestEnum.Value2, response); + } + + [Fact] + public async Task SendPrimitiveHandleEnumIfValueIsFromEnumMember() + { + var mockHandler = new Mock(); + var client = new HttpClient(mockHandler.Object); + mockHandler.Protected() + .Setup>("SendAsync", ItExpr.IsAny(), ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent("Value__3") + }); + + var mockParseNode = new Mock(); + mockParseNode.Setup(x => x.GetStringValue()) + .Returns("Value__3"); + + var mockParseNodeFactory = new Mock(); + mockParseNodeFactory.Setup(x => x.GetRootParseNodeAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(Task.FromResult(mockParseNode.Object)); + var adapter = new HttpClientRequestAdapter(_authenticationProvider, mockParseNodeFactory.Object, httpClient: client); + var requestInfo = new RequestInformation + { + HttpMethod = Method.GET, + URI = new Uri("https://example.com") + }; + + var response = await adapter.SendPrimitiveAsync(requestInfo); + + Assert.Equal(TestEnum.Value3, response); + } + + [Fact] + public async Task SendPrimitiveHandleNullableEnumIfValueIsFromEnumMember() + { + var mockHandler = new Mock(); + var client = new HttpClient(mockHandler.Object); + mockHandler.Protected() + .Setup>("SendAsync", ItExpr.IsAny(), ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent("Value__3") + }); + + var mockParseNode = new Mock(); + mockParseNode.Setup(x => x.GetStringValue()) + .Returns("Value__3"); + + var mockParseNodeFactory = new Mock(); + mockParseNodeFactory.Setup(x => x.GetRootParseNodeAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(Task.FromResult(mockParseNode.Object)); + var adapter = new HttpClientRequestAdapter(_authenticationProvider, mockParseNodeFactory.Object, httpClient: client); + var requestInfo = new RequestInformation + { + HttpMethod = Method.GET, + URI = new Uri("https://example.com") + }; + + var response = await adapter.SendPrimitiveAsync(requestInfo); + + Assert.Equal(TestEnum.Value3, response); + } + + [Fact] + public async Task SendPrimitiveReturnsNullIfValueCannotBeParsedToEnum() + { + var mockHandler = new Mock(); + var client = new HttpClient(mockHandler.Object); + mockHandler.Protected() + .Setup>("SendAsync", ItExpr.IsAny(), ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent("Value0") + }); + + var mockParseNode = new Mock(); + mockParseNode.Setup(x => x.GetStringValue()) + .Returns("Value0"); + + var mockParseNodeFactory = new Mock(); + mockParseNodeFactory.Setup(x => x.GetRootParseNodeAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(Task.FromResult(mockParseNode.Object)); + var adapter = new HttpClientRequestAdapter(_authenticationProvider, mockParseNodeFactory.Object, httpClient: client); + var requestInfo = new RequestInformation + { + HttpMethod = Method.GET, + URI = new Uri("https://example.com") + }; + + var response = await adapter.SendPrimitiveAsync(requestInfo); + + Assert.Null(response); + } + + [Fact] + public async Task SendPrimitiveHandleEnumFlagsIfValuesAreStrings() + { + var mockHandler = new Mock(); + var client = new HttpClient(mockHandler.Object); + mockHandler.Protected() + .Setup>("SendAsync", ItExpr.IsAny(), ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent("Value1,Value3") + }); + + var mockParseNode = new Mock(); + mockParseNode.Setup(x => x.GetStringValue()) + .Returns("Value1,Value3"); + + var mockParseNodeFactory = new Mock(); + mockParseNodeFactory.Setup(x => x.GetRootParseNodeAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(Task.FromResult(mockParseNode.Object)); + var adapter = new HttpClientRequestAdapter(_authenticationProvider, mockParseNodeFactory.Object, httpClient: client); + var requestInfo = new RequestInformation + { + HttpMethod = Method.GET, + URI = new Uri("https://example.com") + }; + + var response = await adapter.SendPrimitiveAsync(requestInfo); + + Assert.Equal(TestEnumWithFlags.Value1 | TestEnumWithFlags.Value3, response); + } + + [Fact] + public async Task SendPrimitiveHandleNullableEnumFlagsIfValuesAreStrings() + { + var mockHandler = new Mock(); + var client = new HttpClient(mockHandler.Object); + mockHandler.Protected() + .Setup>("SendAsync", ItExpr.IsAny(), ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent("Value1,Value3") + }); + + var mockParseNode = new Mock(); + mockParseNode.Setup(x => x.GetStringValue()) + .Returns("Value1,Value3"); + + var mockParseNodeFactory = new Mock(); + mockParseNodeFactory.Setup(x => x.GetRootParseNodeAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(Task.FromResult(mockParseNode.Object)); + var adapter = new HttpClientRequestAdapter(_authenticationProvider, mockParseNodeFactory.Object, httpClient: client); + var requestInfo = new RequestInformation + { + HttpMethod = Method.GET, + URI = new Uri("https://example.com") + }; + + var response = await adapter.SendPrimitiveAsync(requestInfo); + + Assert.Equal(TestEnumWithFlags.Value1 | TestEnumWithFlags.Value3, response); + } + + [Fact] + public async Task SendPrimitiveHandleEnumFlagsIfValuesAreIntegers() + { + var mockHandler = new Mock(); + var client = new HttpClient(mockHandler.Object); + mockHandler.Protected() + .Setup>("SendAsync", ItExpr.IsAny(), ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent("1,2") + }); + + var mockParseNode = new Mock(); + mockParseNode.Setup(x => x.GetStringValue()) + .Returns("1,2"); + + var mockParseNodeFactory = new Mock(); + mockParseNodeFactory.Setup(x => x.GetRootParseNodeAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(Task.FromResult(mockParseNode.Object)); + var adapter = new HttpClientRequestAdapter(_authenticationProvider, mockParseNodeFactory.Object, httpClient: client); + var requestInfo = new RequestInformation + { + HttpMethod = Method.GET, + URI = new Uri("https://example.com") + }; + + var response = await adapter.SendPrimitiveAsync(requestInfo); + + Assert.Equal(TestEnumWithFlags.Value1 | TestEnumWithFlags.Value2, response); + } + + [Fact] + public async Task SendPrimitiveHandleNullableEnumFlagsIfValuesAreIntegers() + { + var mockHandler = new Mock(); + var client = new HttpClient(mockHandler.Object); + mockHandler.Protected() + .Setup>("SendAsync", ItExpr.IsAny(), ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent("1,2") + }); + + var mockParseNode = new Mock(); + mockParseNode.Setup(x => x.GetStringValue()) + .Returns("1,2"); + + var mockParseNodeFactory = new Mock(); + mockParseNodeFactory.Setup(x => x.GetRootParseNodeAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(Task.FromResult(mockParseNode.Object)); + var adapter = new HttpClientRequestAdapter(_authenticationProvider, mockParseNodeFactory.Object, httpClient: client); + var requestInfo = new RequestInformation + { + HttpMethod = Method.GET, + URI = new Uri("https://example.com") + }; + + var response = await adapter.SendPrimitiveAsync(requestInfo); + + Assert.Equal(TestEnumWithFlags.Value1 | TestEnumWithFlags.Value2, response); + } + + [Fact] + public async Task SendPrimitiveHandleEnumFlagsIfValuesAreFromEnumMember() + { + var mockHandler = new Mock(); + var client = new HttpClient(mockHandler.Object); + mockHandler.Protected() + .Setup>("SendAsync", ItExpr.IsAny(), ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent("Value__3,Value__2") + }); + + var mockParseNode = new Mock(); + mockParseNode.Setup(x => x.GetStringValue()) + .Returns("Value__3,Value__2"); + + var mockParseNodeFactory = new Mock(); + mockParseNodeFactory.Setup(x => x.GetRootParseNodeAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(Task.FromResult(mockParseNode.Object)); + var adapter = new HttpClientRequestAdapter(_authenticationProvider, mockParseNodeFactory.Object, httpClient: client); + var requestInfo = new RequestInformation + { + HttpMethod = Method.GET, + URI = new Uri("https://example.com") + }; + + var response = await adapter.SendPrimitiveAsync(requestInfo); + + Assert.Equal(TestEnumWithFlags.Value2 | TestEnumWithFlags.Value3, response); + } + + [Fact] + public async Task SendPrimitiveHandleNullableEnumFlagsIfValuesAreFromEnumMember() + { + var mockHandler = new Mock(); + var client = new HttpClient(mockHandler.Object); + mockHandler.Protected() + .Setup>("SendAsync", ItExpr.IsAny(), ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent("Value__3,Value__2") + }); + + var mockParseNode = new Mock(); + mockParseNode.Setup(x => x.GetStringValue()) + .Returns("Value__3,Value__2"); + + var mockParseNodeFactory = new Mock(); + mockParseNodeFactory.Setup(x => x.GetRootParseNodeAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(Task.FromResult(mockParseNode.Object)); + var adapter = new HttpClientRequestAdapter(_authenticationProvider, mockParseNodeFactory.Object, httpClient: client); + var requestInfo = new RequestInformation + { + HttpMethod = Method.GET, + URI = new Uri("https://example.com") + }; + + var response = await adapter.SendPrimitiveAsync(requestInfo); + + Assert.Equal(TestEnumWithFlags.Value2 | TestEnumWithFlags.Value3, response); + } + + [Fact] + public async Task SendPrimitiveReturnsNullIfFlagValueCannotBeParsedToEnum() + { + var mockHandler = new Mock(); + var client = new HttpClient(mockHandler.Object); + mockHandler.Protected() + .Setup>("SendAsync", ItExpr.IsAny(), ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent("Value0") + }); + + var mockParseNode = new Mock(); + mockParseNode.Setup(x => x.GetStringValue()) + .Returns("Value0"); + + var mockParseNodeFactory = new Mock(); + mockParseNodeFactory.Setup(x => x.GetRootParseNodeAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(Task.FromResult(mockParseNode.Object)); + var adapter = new HttpClientRequestAdapter(_authenticationProvider, mockParseNodeFactory.Object, httpClient: client); + var requestInfo = new RequestInformation + { + HttpMethod = Method.GET, + URI = new Uri("https://example.com") + }; + + var response = await adapter.SendPrimitiveAsync(requestInfo); + + Assert.Null(response); + } + } + + public enum TestEnum + { + [EnumMember(Value = "Value__1")] + Value1, + [EnumMember(Value = "Value__2")] + Value2, + [EnumMember(Value = "Value__3")] + Value3 + } + + [Flags] + public enum TestEnumWithFlags + { + [EnumMember(Value = "Value__1")] + Value1 = 0x01, + [EnumMember(Value = "Value__2")] + Value2 = 0x02, + [EnumMember(Value = "Value__3")] + Value3 = 0x04 } } diff --git a/tests/serialization/json/JsonParseNodeTests.cs b/tests/serialization/json/JsonParseNodeTests.cs index 2cfa976..84ac1c2 100644 --- a/tests/serialization/json/JsonParseNodeTests.cs +++ b/tests/serialization/json/JsonParseNodeTests.cs @@ -105,6 +105,11 @@ public class JsonParseNodeTests " }\r\n" + "}"; + private const string TestCollectionOfEnumsJson = "[\r\n" + + " \"Item2:SubItem1\",\r\n" + + " \"Item3:SubItem1\"\r\n" + + "]"; + private static readonly string TestUserCollectionString = $"[{TestUserJson}]"; [Fact] @@ -287,5 +292,29 @@ public void GetEntityWithUntypedNodesFromJson() } } } + + [Fact] + public void GetCollectionOfEnumValuesFromJson() + { + using var jsonDocument = JsonDocument.Parse(TestCollectionOfEnumsJson); + var rootParseNode = new JsonParseNode(jsonDocument.RootElement); + var values = rootParseNode.GetCollectionOfPrimitiveValues().ToArray(); + // Assert + Assert.NotEmpty(values); + Assert.Equal(TestNamingEnum.Item2SubItem1, values[0]); + Assert.Equal(TestNamingEnum.Item3SubItem1, values[1]); + } + + [Fact] + public void GetCollectionOfNullableEnumValuesFromJson() + { + using var jsonDocument = JsonDocument.Parse(TestCollectionOfEnumsJson); + var rootParseNode = new JsonParseNode(jsonDocument.RootElement); + var values = rootParseNode.GetCollectionOfPrimitiveValues().ToArray(); + // Assert + Assert.NotEmpty(values); + Assert.Equal(TestNamingEnum.Item2SubItem1, values[0]); + Assert.Equal(TestNamingEnum.Item3SubItem1, values[1]); + } } } diff --git a/tests/serialization/text/Mocks/TestEnum.cs b/tests/serialization/text/Mocks/TestEnum.cs new file mode 100644 index 0000000..7ad801d --- /dev/null +++ b/tests/serialization/text/Mocks/TestEnum.cs @@ -0,0 +1,12 @@ +using System.Runtime.Serialization; + +namespace Microsoft.Kiota.Serialization.Text.Tests.Mocks +{ + public enum TestEnum + { + [EnumMember(Value = "Value_1")] + First, + [EnumMember(Value = "Value_2")] + Second, + } +} diff --git a/tests/serialization/text/TextParseNodeTests.cs b/tests/serialization/text/TextParseNodeTests.cs new file mode 100644 index 0000000..e2edb88 --- /dev/null +++ b/tests/serialization/text/TextParseNodeTests.cs @@ -0,0 +1,41 @@ +using Microsoft.Kiota.Serialization.Text.Tests.Mocks; +using Xunit; + +namespace Microsoft.Kiota.Serialization.Text.Tests +{ + public class TextParseNodeTests + { + [Fact] + public void TextParseNode_GetEnumFromInteger() + { + var text = "1"; + var parseNode = new TextParseNode(text); + + var result = parseNode.GetEnumValue(); + + Assert.Equal(TestEnum.Second, result); + } + + [Fact] + public void TextParseNode_GetEnumFromString() + { + var text = "First"; + var parseNode = new TextParseNode(text); + + var result = parseNode.GetEnumValue(); + + Assert.Equal(TestEnum.First, result); + } + + [Fact] + public void TextParseNode_GetEnumFromEnumMember() + { + var text = "Value_2"; + var parseNode = new TextParseNode(text); + + var result = parseNode.GetEnumValue(); + + Assert.Equal(TestEnum.Second, result); + } + } +} diff --git a/tests/trimming/Microsoft.Kiota.Trimming.Validation/Directory.Build.props b/tests/trimming/Microsoft.Kiota.Trimming.Validation/Directory.Build.props new file mode 100644 index 0000000..a36bd59 --- /dev/null +++ b/tests/trimming/Microsoft.Kiota.Trimming.Validation/Directory.Build.props @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/tests/trimming/Microsoft.Kiota.Trimming.Validation/Microsoft.Kiota.Trimming.Validation.csproj b/tests/trimming/Microsoft.Kiota.Trimming.Validation/Microsoft.Kiota.Trimming.Validation.csproj new file mode 100644 index 0000000..44d477f --- /dev/null +++ b/tests/trimming/Microsoft.Kiota.Trimming.Validation/Microsoft.Kiota.Trimming.Validation.csproj @@ -0,0 +1,28 @@ + + + + Exe + net6.0;net8.0 + enable + enable + true + true + + + + + + + + + + + + + + + + + + + diff --git a/tests/trimming/Microsoft.Kiota.Trimming.Validation/Program.cs b/tests/trimming/Microsoft.Kiota.Trimming.Validation/Program.cs new file mode 100644 index 0000000..1e7c3d0 --- /dev/null +++ b/tests/trimming/Microsoft.Kiota.Trimming.Validation/Program.cs @@ -0,0 +1,10 @@ +namespace Microsoft.Kiota.Trimming.Validation +{ + internal class Program + { + static void Main(string[] args) + { + Console.WriteLine("Hello, World!"); + } + } +}