using Amazon.JSII.JsonModel.Spec; using Amazon.JSII.Runtime.Deputy; using Amazon.JSII.Runtime.Services; using Amazon.JSII.Runtime.Services.Converters; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json.Linq; using NSubstitute; using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using Newtonsoft.Json.Converters; using Xunit; namespace Amazon.JSII.Runtime.UnitTests.Deputy.Converters { public sealed class FrameworkToJsiiConverterTests { const string Prefix = "Runtime.Deputy.Converters." + nameof(FrameworkToJsiiConverter) + "."; public abstract class TestBase { internal readonly ITypeCache _typeCache; internal readonly IReferenceMap _referenceMap; internal readonly FrameworkToJsiiConverter _converter; protected TestBase() { _typeCache = Substitute.For<ITypeCache>(); _referenceMap = Substitute.For<IReferenceMap>(); _converter = new FrameworkToJsiiConverter(_typeCache); IServiceCollection serviceCollection = new ServiceCollection(); serviceCollection.AddSingleton<ITypeCache>(_typeCache); serviceCollection.AddSingleton<IReferenceMap>(_referenceMap); serviceCollection.AddSingleton<IFrameworkToJsiiConverter>(_converter); serviceCollection.AddSingleton<IClient>(Substitute.For<IClient>()); serviceCollection.AddTransient<IClient>(sp => Substitute.For<IClient>()); IServiceProvider serviceProvider = serviceCollection.BuildServiceProvider(); ServiceContainer.ServiceProviderOverride = serviceProvider; _typeCache.TryGetClassType("myClassFqn").Returns(typeof(TestClass)); _typeCache.TryGetEnumType("myEnumFqn").Returns(typeof(TestEnum)); } } public sealed class Void : TestBase { const string _Prefix = Prefix + nameof(Void) + "."; [Fact(DisplayName = _Prefix + nameof(DoesNotConvert))] public void DoesNotConvert() { bool success = _converter.TryConvert(null, _referenceMap, null, out object? actual); Assert.True(success); Assert.Null(actual); } [Fact(DisplayName = _Prefix + nameof(ThrowsIfValueIsNotNull))] public void ThrowsIfValueIsNotNull() { Assert.Throws<ArgumentException>("value", () => _converter.TryConvert(null, _referenceMap, "", out object? actual)); } } public sealed class Primitive : TestBase { const string _Prefix = Prefix + nameof(Primitive) + "."; [Theory(DisplayName = _Prefix + nameof(ConvertsPrimitiveValues))] [InlineData(PrimitiveType.Boolean, false, false, false)] [InlineData(PrimitiveType.Boolean, true, true, false)] [InlineData(PrimitiveType.Boolean, false, false, true)] [InlineData(PrimitiveType.Boolean, true, true, true)] [InlineData(PrimitiveType.Boolean, null, null, true)] [InlineData(PrimitiveType.Number, (ushort)42, (double)42, false)] [InlineData(PrimitiveType.Number, (short)42, (double)42, false)] [InlineData(PrimitiveType.Number, (uint)42, (double)42, false)] [InlineData(PrimitiveType.Number, 42, (double)42, false)] [InlineData(PrimitiveType.Number, (ulong)42, (double)42, false)] [InlineData(PrimitiveType.Number, (long)42, (double)42, false)] [InlineData(PrimitiveType.Number, (float)42, (double)42, false)] [InlineData(PrimitiveType.Number, (double)42, (double)42, false)] [InlineData(PrimitiveType.Number, (ushort)42, (double)42, true)] [InlineData(PrimitiveType.Number, (short)42, (double)42, true)] [InlineData(PrimitiveType.Number, (uint)42, (double)42, true)] [InlineData(PrimitiveType.Number, 42, (double)42, true)] [InlineData(PrimitiveType.Number, (ulong)42, (double)42, true)] [InlineData(PrimitiveType.Number, (long)42, (double)42, true)] [InlineData(PrimitiveType.Number, (float)42, (double)42, true)] [InlineData(PrimitiveType.Number, (double)42, (double)42, true)] [InlineData(PrimitiveType.Number, null, null, true)] [InlineData(PrimitiveType.String, "", "", false)] [InlineData(PrimitiveType.String, "a", "a", false)] [InlineData(PrimitiveType.String, "abc", "abc", false)] [InlineData(PrimitiveType.String, null, null, false)] [InlineData(PrimitiveType.String, "", "", true)] [InlineData(PrimitiveType.String, "a", "a", true)] [InlineData(PrimitiveType.String, "abc", "abc", true)] [InlineData(PrimitiveType.String, null, null, true)] public void ConvertsPrimitiveValues(PrimitiveType primitive, object value, object expected, bool isOptional) { var instance = new OptionalValue(new TypeReference(primitive: primitive), isOptional: isOptional); bool success = _converter.TryConvert(instance, _referenceMap, value, out object? actual); Assert.True(success); Assert.Equal(expected, actual); } [Fact(DisplayName = _Prefix + nameof(ConvertsDateValues))] public void ConvertsDateValues() { var instance = new OptionalValue(new TypeReference(primitive: PrimitiveType.Date)); DateTime now = DateTime.Now; bool success = _converter.TryConvert(instance, _referenceMap, now, out object? actual); Assert.True(success); Assert.IsType<JObject>(actual); var expected = new JObject { new JProperty("$jsii.date", now.ToString("yyyy-MM-ddTHH\\:mm\\:ss.fffffffzzz", CultureInfo.InvariantCulture)) }; Assert.Equal(expected, actual); success = _converter.TryConvert(instance, _referenceMap, null, out actual); Assert.False(success); Assert.Null(actual); } [Fact(DisplayName = _Prefix + nameof(ConvertsOptionalDateValues))] public void ConvertsOptionalDateValues() { var instance = new OptionalValue(new TypeReference(primitive: PrimitiveType.Date), isOptional: true); DateTime now = DateTime.Now; bool success = _converter.TryConvert(instance, _referenceMap, now, out object? actual); Assert.True(success); Assert.IsType<JObject>(actual); var expected = new JObject { new JProperty("$jsii.date", now.ToString("yyyy-MM-ddTHH\\:mm\\:ss.fffffffzzz", CultureInfo.InvariantCulture)) }; Assert.Equal(expected, actual); success = _converter.TryConvert(instance, _referenceMap, null, out actual); Assert.True(success); Assert.Null(actual); } [Fact(DisplayName = _Prefix + nameof(ConvertsJsonValues))] public void ConvertsJsonValues() { var instance = new OptionalValue(new TypeReference(primitive: PrimitiveType.Json)); JObject jObject = new JObject( new JProperty("myArray", new JArray( new JValue(42), new JValue("myString"), new JValue(false), new JObject(), new JArray(), new JObject( new JProperty("nested1", "value1"), new JProperty("nested2", "value2") ) )) ); bool success = _converter.TryConvert(instance, _referenceMap, jObject, out object? actual); Assert.True(success); Assert.Same(jObject, actual); } [Fact(DisplayName = _Prefix + nameof(FailsOnNullBoolean))] public void FailsOnNullBoolean() { var instance = new OptionalValue(new TypeReference(primitive: PrimitiveType.Boolean)); bool success = _converter.TryConvert(instance, _referenceMap, null, out object? actual); Assert.False(success); } [Fact(DisplayName = _Prefix + nameof(FailsOnNullNumber))] public void FailsOnNullNumber() { var instance = new OptionalValue(new TypeReference(primitive: PrimitiveType.Number)); bool success = _converter.TryConvert(instance, _referenceMap, null, out object? actual); Assert.False(success); } [Fact(DisplayName = _Prefix + nameof(ConvertsNullJson))] public void ConvertsNullJson() { var instance = new OptionalValue(new TypeReference(primitive: PrimitiveType.Json)); bool success = _converter.TryConvert(instance, _referenceMap, null, out object? actual); Assert.True(success); Assert.Null(actual); } } public sealed class FullyQualifiedName : TestBase { const string _Prefix = Prefix + nameof(FullyQualifiedName) + "."; [Fact(DisplayName = _Prefix + nameof(ConvertsClassReference))] public void ConvertsClassReference() { var instance = new OptionalValue(new TypeReference("myClassFqn")); ByRefValue byRef = new ByRefValue("myClassFqn", "0001"); TestClass myClass = new TestClass(byRef); bool success = _converter.TryConvert(instance, _referenceMap, myClass, out object? actual); Assert.True(success); Assert.Equal(JObject.FromObject(byRef), actual); _referenceMap.Received().AddNativeReference(byRef, myClass); } [Fact(DisplayName = _Prefix + nameof(ConvertsEnumValue))] public void ConvertsEnumValue() { var instance = new OptionalValue(new TypeReference("myEnumFqn")); TestEnum myEnum = TestEnum.MyMember2; bool success = _converter.TryConvert(instance, _referenceMap, myEnum, out object? actual); Assert.True(success); Assert.IsType<JObject>(actual); var expected = new JObject { new JProperty("$jsii.enum", "myEnumFqn/MyMember2") }; Assert.Equal(expected, actual); } [Fact(DisplayName = _Prefix + nameof(ConvertsNullClassReference))] public void ConvertsNullClassReference() { var instance = new OptionalValue(new TypeReference("myClassFqn")); bool success = _converter.TryConvert(instance, _referenceMap, null, out object? actual); Assert.True(success); Assert.Null(actual); } [Fact(DisplayName = _Prefix + nameof(DoesNotConvertNullNonOptionalEnumValue))] public void DoesNotConvertNullNonOptionalEnumValue() { var instance = new OptionalValue(new TypeReference("myEnumFqn")); bool success =_converter.TryConvert(instance, _referenceMap, null, out object? actual); Assert.False(success); Assert.Null(actual); } [Fact(DisplayName = _Prefix + nameof(ConvertsNullOptionalEnumValue))] public void ConvertsNullOptionalEnumValue() { var instance = new OptionalValue(new TypeReference("myEnumFqn"), isOptional: true); bool success = _converter.TryConvert(instance, _referenceMap, null, out object? actual); Assert.True(success); Assert.Null(actual); } } public sealed class Collection : TestBase { const string _Prefix = Prefix + nameof(Collection) + "."; [Fact(DisplayName = _Prefix + nameof(ConvertsMap))] public void ConvertsMap() { var instance = new OptionalValue(new TypeReference( collection: new CollectionTypeReference(CollectionKind.Map, new TypeReference(primitive: PrimitiveType.String) ) )); IDictionary<string, object> frameworkMap = new Dictionary<string, object> { { "myKey1", "myValue1" }, { "myKey2", "myValue2" } }; bool success = _converter.TryConvert(instance, _referenceMap, frameworkMap, out object? actual); Assert.True(success); Assert.IsType<JObject>(actual); var expected = JObject.Parse(@"{ ""$jsii.map"": { ""myKey1"": ""myValue1"", ""myKey2"": ""myValue2"" } }"); Assert.True(JToken.DeepEquals(expected, actual as JObject), $"Expected: {expected}\nActual: {actual}"); } [Fact(DisplayName = _Prefix + nameof(RecursivelyConvertsMapElements))] public void RecursivelyConvertsMapElements() { var instance = new OptionalValue(new TypeReference( collection: new CollectionTypeReference(CollectionKind.Map, new TypeReference( collection: new CollectionTypeReference(CollectionKind.Map, new TypeReference(primitive: PrimitiveType.String) ) ) ) )); var frameworkMap = new Dictionary<string, IDictionary<string, string>> { { "myKey1", new Dictionary<string, string> { { "mySubKey1", "myValue1" } } }, { "myKey2", new Dictionary<string, string> { { "mySubKey2", "myValue2" } } }, }; bool success = _converter.TryConvert(instance, _referenceMap, frameworkMap, out object? actual); Assert.True(success); Assert.IsType<JObject>(actual); var expected = JObject.Parse(@"{ ""$jsii.map"": { ""myKey1"": { ""$jsii.map"": { ""mySubKey1"": ""myValue1"" } }, ""myKey2"": { ""$jsii.map"": { ""mySubKey2"": ""myValue2"" } } } }"); Assert.True(JToken.DeepEquals(expected, actual as JObject), $"Expected: {expected}\nActual: {actual}"); } [Fact(DisplayName = _Prefix + nameof(ConvertsArray))] public void ConvertsArray() { var instance = new OptionalValue(new TypeReference ( collection: new CollectionTypeReference(CollectionKind.Array, new TypeReference(primitive: PrimitiveType.String) ) )); string[] frameworkArray = new string[] { "myValue1", "myValue2", }; bool success = _converter.TryConvert(instance, _referenceMap, frameworkArray, out object? actual); Assert.True(success); Assert.IsType<JArray>(actual); Assert.Collection ( (JArray)actual!, value => Assert.Equal("myValue1", value), value => Assert.Equal("myValue2", value) ); } [Fact(DisplayName = _Prefix + nameof(RecursivelyConvertsArrayElements))] public void RecursivelyConvertsArrayElements() { var instance = new OptionalValue(new TypeReference ( collection: new CollectionTypeReference(CollectionKind.Array, new TypeReference ( collection: new CollectionTypeReference(CollectionKind.Array, new TypeReference(primitive: PrimitiveType.String) ) ) ) )); var frameworkArray = new string[][] { new [] { "myValue1" }, new [] { "myValue2" }, }; bool success = _converter.TryConvert(instance, _referenceMap, frameworkArray, out object? actual); Assert.True(success); Assert.IsType<JArray>(actual); Assert.Collection( (JArray)actual!, value => { Assert.IsType<JArray>(value); Assert.Collection ( (JArray)value, subValue => Assert.Equal("myValue1", subValue) ); }, value => { Assert.IsType<JArray>(value); Assert.Collection( (JArray)value, subValue => Assert.Equal("myValue2", subValue) ); } ); } [Fact(DisplayName = _Prefix + nameof(RecursivelyConvertsMapElementsWithMapOfAny))] public void RecursivelyConvertsMapElementsWithMapOfAny() { var instance = new OptionalValue(new TypeReference( collection: new CollectionTypeReference(CollectionKind.Map, new TypeReference( collection: new CollectionTypeReference(CollectionKind.Map, new TypeReference(primitive: PrimitiveType.Any) ) ) ) )); var frameworkMap = new Dictionary<string, IDictionary<string, object>> { { "myKey1", new Dictionary<string, object> { { "mySubKey1", "myValue1" } } }, { "myKey2", new Dictionary<string, object> { { "mySubKey2", "myValue2" } } }, }; // This will test the call to FrameworkToJsiiConverter.TryConvertCollectionElement() // In the case of a of a Map of Map of Any bool success = _converter.TryConvert(instance, _referenceMap, frameworkMap, out object? actual); Assert.True(success); Assert.IsType<JObject>(actual); var expected = JObject.Parse(@"{ ""$jsii.map"": { ""myKey1"": { ""$jsii.map"": { ""mySubKey1"": ""myValue1"" } }, ""myKey2"": { ""$jsii.map"": { ""mySubKey2"": ""myValue2"" } } } }"); Assert.True(JToken.DeepEquals(expected, actual as JObject), $"Expected: {expected}\nActual: {actual}"); } [Fact(DisplayName = _Prefix + nameof(RecursivelyConvertsMapElementsWithArrayOfAny))] public void RecursivelyConvertsMapElementsWithArrayOfAny() { var instance = new OptionalValue(new TypeReference ( collection: new CollectionTypeReference(CollectionKind.Map, new TypeReference ( collection: new CollectionTypeReference(CollectionKind.Array, new TypeReference(primitive: PrimitiveType.Any) ) ) ) )); var frameworkArray = new Dictionary<string, object>() { {"key", new [] { "true" }}, {"key2", new [] { false }}, }; // This will test the call to FrameworkToJsiiConverter.TryConvertCollectionElement() // In the case of a of a Map of Array of Any bool success = _converter.TryConvert(instance, _referenceMap, frameworkArray, out object? actual); Assert.True(success); Assert.IsType<JObject>(actual); var expected = JObject.Parse(@"{ ""$jsii.map"": { ""key"": [""true""], ""key2"": [false] } }"); Assert.True(JToken.DeepEquals(expected, actual as JObject), $"Expected: {expected}\nActual: {actual}"); } [Fact(DisplayName = _Prefix + nameof(ConvertsNullMap))] public void ConvertsNullMap() { var instance = new OptionalValue(new TypeReference ( collection: new CollectionTypeReference(CollectionKind.Map, new TypeReference(primitive: PrimitiveType.String) ) )); bool success = _converter.TryConvert(instance, _referenceMap, null, out object? actual); Assert.True(success); Assert.Null(actual); } [Fact(DisplayName = _Prefix + nameof(ConvertsNullArray))] public void ConvertsNullArray() { var instance = new OptionalValue(new TypeReference ( collection: new CollectionTypeReference(CollectionKind.Array, new TypeReference(primitive: PrimitiveType.String) ) )); bool success = _converter.TryConvert(instance, _referenceMap, null, out object? actual); Assert.True(success); Assert.Null(actual); } } public sealed class Union : TestBase { const string _Prefix = Prefix + nameof(Union) + "."; [Fact(DisplayName = _Prefix + nameof(FailsIfNoTypeMatches))] public void FailsIfNoTypeMatches() { var instance = new OptionalValue(new TypeReference( union: new UnionTypeReference(new[] { new TypeReference(primitive: PrimitiveType.String), new TypeReference(primitive: PrimitiveType.Number) }) )); bool success = _converter.TryConvert(instance, _referenceMap, true, out object? actual); Assert.False(success); Assert.Null(actual); } [Fact(DisplayName = _Prefix + nameof(ConvertsSimpleUnion))] public void ConvertsSimpleUnion() { var instance = new OptionalValue(new TypeReference( union: new UnionTypeReference(new[] { new TypeReference(primitive: PrimitiveType.String) }) )); bool success = _converter.TryConvert(instance, _referenceMap, "abc", out object? actual); Assert.True(success); Assert.IsType<string>(actual); Assert.Equal("abc", actual); } [Fact(DisplayName = _Prefix + nameof(ConvertsAsFirstMatchingType))] public void ConvertsAsFirstMatchingType() { var instance = new OptionalValue(new TypeReference( union: new UnionTypeReference(new[] { new TypeReference(primitive: PrimitiveType.String), new TypeReference(primitive: PrimitiveType.Number) }) )); bool success = _converter.TryConvert(instance, _referenceMap, (ushort)7, out object? actual); Assert.True(success); Assert.IsType<double>(actual); Assert.Equal((double)7, actual); } [Fact(DisplayName = _Prefix + nameof(ConvertsNull))] public void ConvertsNull() { var instance = new OptionalValue(new TypeReference( union: new UnionTypeReference(new[] { new TypeReference(primitive: PrimitiveType.String) }) )); bool success = _converter.TryConvert(instance, _referenceMap, null, out object? actual); Assert.True(success); Assert.Null(actual); } } public sealed class Any : TestBase { const string _Prefix = Prefix + nameof(Any) + "."; [Fact(DisplayName = _Prefix + nameof(ConvertsNull))] public void ConvertsNull() { var instance = new OptionalValue(new TypeReference(primitive: PrimitiveType.Any)); bool success = _converter.TryConvert(instance, _referenceMap, null, out object? actual); Assert.True(success); Assert.Null(actual); } [Theory(DisplayName = _Prefix + nameof(ConvertsPrimitive))] [InlineData("myString", "myString")] [InlineData((ushort)5, (double)5)] [InlineData(true, true)] public void ConvertsPrimitive(object value, object expected) { var instance = new OptionalValue(new TypeReference(primitive: PrimitiveType.Any)); bool success = _converter.TryConvert(instance, _referenceMap, value, out object? actual); Assert.True(success); Assert.Equal(expected, actual); } [Fact(DisplayName = _Prefix + nameof(ConvertsDate))] public void ConvertsDate() { var instance = new OptionalValue(new TypeReference(primitive: PrimitiveType.Any)); DateTime now = DateTime.Now; bool success = _converter.TryConvert(instance, _referenceMap, now, out object? actual); Assert.True(success); Assert.IsType<JObject>(actual); var expected = new JObject { new JProperty("$jsii.date", now.ToString("yyyy-MM-ddTHH\\:mm\\:ss.fffffffzzz", CultureInfo.InvariantCulture)) }; Assert.Equal(expected, actual); } [Fact(DisplayName = _Prefix + nameof(ConvertsJson))] public void ConvertsJson() { var instance = new OptionalValue(new TypeReference(primitive: PrimitiveType.Any)); JObject value = new JObject(new JProperty("myKey", "myValue")); bool success = _converter.TryConvert(instance, _referenceMap, value, out object? actual); Assert.True(success); Assert.Same(value, actual); } [Fact(DisplayName = _Prefix + nameof(ConvertsMap))] public void ConvertsMap() { var instance = new OptionalValue(new TypeReference(primitive: PrimitiveType.Any)); IDictionary<string, string> value = new Dictionary<string, string> { { "myKey", "myValue" } }; bool success = _converter.TryConvert(instance, _referenceMap, value, out object? actual); Assert.True(success); Assert.IsType<JObject>(actual); Assert.Collection((IEnumerable<KeyValuePair<string, JToken>>)actual!, kvp => { Assert.IsType<JObject>(kvp.Value); Assert.Equal("$jsii.map", kvp.Key, ignoreLineEndingDifferences: true); Assert.Collection((IEnumerable<KeyValuePair<string, JToken>>)kvp.Value, nkvp => { Assert.IsType<JValue>(nkvp.Value); Assert.Equal("myKey", nkvp.Key, ignoreLineEndingDifferences: true); Assert.Equal("myValue", nkvp.Value.Value<string>(), ignoreLineEndingDifferences: true); }); } ); } [Fact(DisplayName = _Prefix + nameof(ConvertsArray))] public void ConvertsArray() { var instance = new OptionalValue(new TypeReference(primitive: PrimitiveType.Any)); string[] value = new[] { "myValue" }; bool success = _converter.TryConvert(instance, _referenceMap, value, out object? actual); Assert.True(success); Assert.IsType<JArray>(actual); Assert.Collection((IEnumerable<JToken>)actual!, element => Assert.Equal("myValue", element.Value<string>(), ignoreLineEndingDifferences: true) ); } } } }