diff --git a/src/CoreEx/Mapping/Converters/EncodedStringToUInt32Converter.cs b/src/CoreEx/Mapping/Converters/EncodedStringToUInt32Converter.cs index 21239ed4..0460b95b 100644 --- a/src/CoreEx/Mapping/Converters/EncodedStringToUInt32Converter.cs +++ b/src/CoreEx/Mapping/Converters/EncodedStringToUInt32Converter.cs @@ -28,12 +28,6 @@ public EncodedStringToUInt32Converter() { } /// public IValueConverter ToSource => _convertToSource; - /// - public readonly object? ConvertToDestination(object? source) => ConvertToDestination((string?)source); - - /// - public readonly object? ConvertToSource(object? destination) => ConvertToSource((uint)destination!); - /// public readonly uint ConvertToDestination(string? source) => ToDestination.Convert(source); diff --git a/src/CoreEx/Mapping/Converters/IConverterT.cs b/src/CoreEx/Mapping/Converters/IConverterT.cs index 5f3ad123..0ddb28fe 100644 --- a/src/CoreEx/Mapping/Converters/IConverterT.cs +++ b/src/CoreEx/Mapping/Converters/IConverterT.cs @@ -19,6 +19,12 @@ public interface IConverter : ISourceConverter, /// object? IDestinationConverter.ConvertToSource(TDestination destination) => ConvertToSource((TDestination)destination!); + /// + object? IConverter.ConvertToDestination(object? source) => ConvertToDestination((TSource)source!); + + /// + object? IConverter.ConvertToSource(object? destination) => ConvertToSource((TDestination)destination!); + /// /// Gets the source to destination . /// diff --git a/src/CoreEx/Mapping/Converters/JsonElementStringConverter.cs b/src/CoreEx/Mapping/Converters/JsonElementStringConverter.cs index 557f13a9..47092daf 100644 --- a/src/CoreEx/Mapping/Converters/JsonElementStringConverter.cs +++ b/src/CoreEx/Mapping/Converters/JsonElementStringConverter.cs @@ -35,12 +35,6 @@ public JsonElementStringConverter() { } /// public IValueConverter ToSource => _convertToSource; - /// - public readonly object? ConvertToDestination(object? source) => ConvertToDestination((string?)source); - - /// - public readonly object? ConvertToSource(object? destination) => ConvertToSource((JsonElement?)destination); - /// public readonly string? ConvertToDestination(JsonElement? source) => ToDestination.Convert(source); diff --git a/src/CoreEx/Mapping/Converters/StringBase64Converter.cs b/src/CoreEx/Mapping/Converters/StringBase64Converter.cs index be83a475..129150ed 100644 --- a/src/CoreEx/Mapping/Converters/StringBase64Converter.cs +++ b/src/CoreEx/Mapping/Converters/StringBase64Converter.cs @@ -28,12 +28,6 @@ public StringBase64Converter() { } /// public IValueConverter ToSource => _convertToSource; - /// - public readonly object? ConvertToDestination(object? source) => ConvertToDestination((string?)source); - - /// - public readonly object? ConvertToSource(object? destination) => ConvertToSource((byte[]?)destination); - /// public readonly byte[]? ConvertToDestination(string? source) => ToDestination.Convert(source); diff --git a/src/CoreEx/Mapping/Converters/TypeToJsonStringConverter.cs b/src/CoreEx/Mapping/Converters/TypeToJsonStringConverter.cs index 1d8b23c6..5d843a1c 100644 --- a/src/CoreEx/Mapping/Converters/TypeToJsonStringConverter.cs +++ b/src/CoreEx/Mapping/Converters/TypeToJsonStringConverter.cs @@ -29,12 +29,6 @@ public TypeToJsonStringConverter() { } /// public IValueConverter ToSource => _convertToSource; - /// - public readonly object? ConvertToDestination(object? source) => ConvertToDestination((T)source!); - - /// - public readonly object? ConvertToSource(object? destination) => ConvertToSource((string?)destination); - /// public readonly string? ConvertToDestination(T source) => ToDestination.Convert(source); diff --git a/tests/CoreEx.Test.Unit/Mapping/Converters/EncodedStringToUInt32ConverterTests.cs b/tests/CoreEx.Test.Unit/Mapping/Converters/EncodedStringToUInt32ConverterTests.cs new file mode 100644 index 00000000..7d6360a3 --- /dev/null +++ b/tests/CoreEx.Test.Unit/Mapping/Converters/EncodedStringToUInt32ConverterTests.cs @@ -0,0 +1,75 @@ +using CoreEx.Mapping.Converters; + +namespace CoreEx.Test.Unit.Mapping.Converters; + +[TestFixture] +public class EncodedStringToUInt32ConverterTests +{ + private readonly EncodedStringToUInt32Converter _converter = EncodedStringToUInt32Converter.Default; + + [Test] + public void ConvertToDestination_ValidBase64String_ReturnsUInt32() + { + var value = 12345u; + var base64 = Convert.ToBase64String(BitConverter.GetBytes(value)); + + _converter.ConvertToDestination(base64).Should().Be(value); + } + + [Test] + public void ConvertToDestination_Null_ReturnsZero() + { + _converter.ConvertToDestination((string?)null).Should().Be(0u); + } + + [Test] + public void ConvertToSource_ValidUInt32_ReturnsBase64String() + { + var value = 98765u; + + var result = _converter.ConvertToSource(value); + + result.Should().Be(Convert.ToBase64String(BitConverter.GetBytes(value))); + } + + [Test] + public void ConvertToSource_Zero_ReturnsNull() + { + _converter.ConvertToSource(0u).Should().BeNull(); + } + + [Test] + public void RoundTrip_UInt32ToBase64AndBack() + { + var value = 555u; + var base64 = _converter.ConvertToSource(value); + var roundTrip = _converter.ConvertToDestination(base64); + + roundTrip.Should().Be(value); + } + + // The following two tests exercise the non-generic IConverter object-based overloads directly (see issue #175, + // which found the exact same defect class in JsonElementStringConverter's non-generic overloads). + [Test] + public void IConverter_ConvertToDestination_Object_ReturnsUInt32() + { + IConverter converter = _converter; + var value = 4242u; + var base64 = Convert.ToBase64String(BitConverter.GetBytes(value)); + + var result = converter.ConvertToDestination(base64); + + result.Should().Be(value); + } + + [Test] + public void IConverter_ConvertToSource_Object_ReturnsBase64String() + { + IConverter converter = _converter; + var value = 111u; + + var result = converter.ConvertToSource(value); + + result.Should().Be(Convert.ToBase64String(BitConverter.GetBytes(value))); + } +} diff --git a/tests/CoreEx.Test.Unit/Mapping/Converters/JsonElementStringConverterTests.cs b/tests/CoreEx.Test.Unit/Mapping/Converters/JsonElementStringConverterTests.cs new file mode 100644 index 00000000..50c7ec44 --- /dev/null +++ b/tests/CoreEx.Test.Unit/Mapping/Converters/JsonElementStringConverterTests.cs @@ -0,0 +1,77 @@ +using System.Text.Json; +using CoreEx.Mapping.Converters; + +namespace CoreEx.Test.Unit.Mapping.Converters; + +[TestFixture] +public class JsonElementStringConverterTests +{ + private readonly JsonElementStringConverter _converter = JsonElementStringConverter.Default; + + [Test] + public void ConvertToDestination_Value_ReturnsJson() + { + using var doc = JsonDocument.Parse("""{"name":"Bob","number":42}"""); + var result = _converter.ConvertToDestination(doc.RootElement); + + result.Should().Be("""{"name":"Bob","number":42}"""); + } + + [Test] + public void ConvertToDestination_Null_ReturnsNull() + { + _converter.ConvertToDestination((JsonElement?)null).Should().BeNull(); + } + + [Test] + public void ConvertToSource_Json_ReturnsJsonElement() + { + var result = _converter.ConvertToSource("""{"name":"Bob","number":42}"""); + + result!.Value.GetProperty("name").GetString().Should().Be("Bob"); + result.Value.GetProperty("number").GetInt32().Should().Be(42); + } + + [Test] + public void ConvertToSource_Null_ReturnsNull() + { + _converter.ConvertToSource((string?)null).Should().BeNull(); + } + + [Test] + public void RoundTrip_JsonElementToStringAndBack() + { + using var doc = JsonDocument.Parse("""{"name":"Alice","number":7}"""); + var json = _converter.ConvertToDestination(doc.RootElement); + var roundTrip = _converter.ConvertToSource(json); + + roundTrip!.Value.GetProperty("name").GetString().Should().Be("Alice"); + roundTrip.Value.GetProperty("number").GetInt32().Should().Be(7); + } + + // The following two tests exercise the non-generic IConverter object-based overloads directly, which is + // where a copy/paste bug (issue #175) previously caused an InvalidCastException or infinite recursion + // (StackOverflowException) because the casts were against the wrong side's type (TDestination instead of + // TSource, and vice versa). + [Test] + public void IConverter_ConvertToDestination_Object_ReturnsJson() + { + IConverter converter = _converter; + using var doc = JsonDocument.Parse("""{"name":"Bob","number":42}"""); + + var result = converter.ConvertToDestination(doc.RootElement); + + result.Should().Be("""{"name":"Bob","number":42}"""); + } + + [Test] + public void IConverter_ConvertToSource_Object_ReturnsJsonElement() + { + IConverter converter = _converter; + + var result = (JsonElement?)converter.ConvertToSource("""{"name":"Bob","number":42}"""); + + result!.Value.GetProperty("name").GetString().Should().Be("Bob"); + result.Value.GetProperty("number").GetInt32().Should().Be(42); + } +} diff --git a/tests/CoreEx.Test.Unit/Mapping/Converters/StringToBase64ConverterTests.cs b/tests/CoreEx.Test.Unit/Mapping/Converters/StringToBase64ConverterTests.cs index 0c968a53..10e96fc1 100644 --- a/tests/CoreEx.Test.Unit/Mapping/Converters/StringToBase64ConverterTests.cs +++ b/tests/CoreEx.Test.Unit/Mapping/Converters/StringToBase64ConverterTests.cs @@ -56,4 +56,29 @@ public void ConvertToDestination_InvalidBase64_ThrowsFormatException() Action act = () => _converter.ConvertToDestination("not_base64!"); act.Should().Throw(); } + + // The following two tests exercise the non-generic IConverter object-based overloads directly (see issue #175, + // which found the exact same defect class in JsonElementStringConverter's non-generic overloads). + [Test] + public void IConverter_ConvertToDestination_Object_ReturnsBytes() + { + IConverter converter = _converter; + var text = "Hello, World!"; + var base64 = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(text)); + + var result = (byte[]?)converter.ConvertToDestination(base64); + + System.Text.Encoding.UTF8.GetString(result!).Should().Be(text); + } + + [Test] + public void IConverter_ConvertToSource_Object_ReturnsBase64String() + { + IConverter converter = _converter; + var bytes = System.Text.Encoding.UTF8.GetBytes("Test123"); + + var result = converter.ConvertToSource(bytes); + + result.Should().Be(Convert.ToBase64String(bytes)); + } } \ No newline at end of file