diff --git a/src/Scarlet.System.Text.Json.DateTimeConverter.Tests/ConverterTests.cs b/src/Scarlet.System.Text.Json.DateTimeConverter.Tests/ConverterTests.cs index e0a2104..f686ae1 100644 --- a/src/Scarlet.System.Text.Json.DateTimeConverter.Tests/ConverterTests.cs +++ b/src/Scarlet.System.Text.Json.DateTimeConverter.Tests/ConverterTests.cs @@ -33,9 +33,9 @@ public void DateTimeConverter_Read_ValidFormat_Success() // Arrange var options = new JsonSerializerOptions { - Converters = { new JsonDateTimeConverterAttribute("yyyy-MM-dd").CreateConverter(typeof(DateTime)) } + Converters = { new JsonDateTimeConverterAttribute("MM/dd/yyyy").CreateConverter(typeof(DateTime)) } }; - var json = "\"2023-10-15\""; + var json = "\"10/15/2023\""; // Act var result = JsonSerializer.Deserialize(json, options); @@ -50,7 +50,7 @@ public void DateTimeConverter_Read_InvalidFormat_FallbackToGetDateTime() // Arrange var options = new JsonSerializerOptions { - Converters = { new JsonDateTimeConverterAttribute("yyyy-MM-dd").CreateConverter(typeof(DateTime)) } + Converters = { new JsonDateTimeConverterAttribute("dd-MM-yyyy").CreateConverter(typeof(DateTime)) } }; // ISO 8601 format that doesn't match our custom format var json = "\"2023-10-15T14:30:45Z\""; @@ -107,7 +107,7 @@ public void DateTimeNullableConverter_Write_ValidValue_Success() // Arrange var options = new JsonSerializerOptions { - Converters = { new JsonDateTimeConverterAttribute("yyyy-MM-dd").CreateConverter(typeof(DateTime?)) } + Converters = { new JsonDateTimeConverterAttribute("yyyy/MM/dd").CreateConverter(typeof(DateTime?)) } }; DateTime? date = new DateTime(2023, 10, 15, 14, 30, 45, DateTimeKind.Utc); @@ -115,7 +115,7 @@ public void DateTimeNullableConverter_Write_ValidValue_Success() var json = JsonSerializer.Serialize(date, options); // Assert - Assert.Equal("\"2023-10-15\"", json); + Assert.Equal("\"2023/10/15\"", json); } [Fact] @@ -124,7 +124,7 @@ public void DateTimeNullableConverter_Write_NullValue_Success() // Arrange var options = new JsonSerializerOptions { - Converters = { new JsonDateTimeConverterAttribute("yyyy-MM-dd").CreateConverter(typeof(DateTime?)) } + Converters = { new JsonDateTimeConverterAttribute("yyyyMMdd").CreateConverter(typeof(DateTime?)) } }; DateTime? date = null; @@ -141,9 +141,9 @@ public void DateTimeNullableConverter_Read_ValidFormat_Success() // Arrange var options = new JsonSerializerOptions { - Converters = { new JsonDateTimeConverterAttribute("yyyy-MM-dd").CreateConverter(typeof(DateTime?)) } + Converters = { new JsonDateTimeConverterAttribute("dd.MM.yyyy").CreateConverter(typeof(DateTime?)) } }; - var json = "\"2023-10-15\""; + var json = "\"15.10.2023\""; // Act var result = JsonSerializer.Deserialize(json, options); @@ -159,7 +159,7 @@ public void DateTimeNullableConverter_Read_NullToken_ReturnsNull() // Arrange var options = new JsonSerializerOptions { - Converters = { new JsonDateTimeConverterAttribute("yyyy-MM-dd").CreateConverter(typeof(DateTime?)) } + Converters = { new JsonDateTimeConverterAttribute("yyyy.MM.dd").CreateConverter(typeof(DateTime?)) } }; var json = "null"; @@ -176,7 +176,7 @@ public void DateTimeNullableConverter_Read_InvalidFormat_ReturnsNull() // Arrange var options = new JsonSerializerOptions { - Converters = { new JsonDateTimeConverterAttribute("yyyy-MM-dd").CreateConverter(typeof(DateTime?)) } + Converters = { new JsonDateTimeConverterAttribute("MM-dd-yyyy").CreateConverter(typeof(DateTime?)) } }; var json = "\"invalid-date\""; @@ -211,7 +211,7 @@ public void DateTimeNullableConverter_RoundTrip_WithNull_Success() // Arrange var options = new JsonSerializerOptions { - Converters = { new JsonDateTimeConverterAttribute("yyyy-MM-dd").CreateConverter(typeof(DateTime?)) } + Converters = { new JsonDateTimeConverterAttribute("dd/MM/yyyy").CreateConverter(typeof(DateTime?)) } }; DateTime? original = null; @@ -267,7 +267,7 @@ public void DateTimeOffsetConverter_Read_InvalidFormat_FallbackToGetDateTimeOffs // Arrange var options = new JsonSerializerOptions { - Converters = { new JsonDateTimeConverterAttribute("yyyy-MM-dd").CreateConverter(typeof(DateTimeOffset)) } + Converters = { new JsonDateTimeConverterAttribute("yyyyMMdd").CreateConverter(typeof(DateTimeOffset)) } }; // Standard ISO 8601 format var json = "\"2023-10-15T14:30:45+00:00\""; @@ -324,7 +324,7 @@ public void DateTimeOffsetNullableConverter_Write_NullValue_Success() // Arrange var options = new JsonSerializerOptions { - Converters = { new JsonDateTimeConverterAttribute("yyyy-MM-dd").CreateConverter(typeof(DateTimeOffset?)) } + Converters = { new JsonDateTimeConverterAttribute("yyyy/MM/dd").CreateConverter(typeof(DateTimeOffset?)) } }; DateTimeOffset? date = null; @@ -359,7 +359,7 @@ public void DateTimeOffsetNullableConverter_Read_NullToken_ReturnsNull() // Arrange var options = new JsonSerializerOptions { - Converters = { new JsonDateTimeConverterAttribute("yyyy-MM-dd").CreateConverter(typeof(DateTimeOffset?)) } + Converters = { new JsonDateTimeConverterAttribute("dd-MM-yyyy").CreateConverter(typeof(DateTimeOffset?)) } }; var json = "null"; @@ -376,7 +376,7 @@ public void DateTimeOffsetNullableConverter_Read_InvalidFormat_ReturnsNull() // Arrange var options = new JsonSerializerOptions { - Converters = { new JsonDateTimeConverterAttribute("yyyy-MM-dd").CreateConverter(typeof(DateTimeOffset?)) } + Converters = { new JsonDateTimeConverterAttribute("MM/dd/yyyy").CreateConverter(typeof(DateTimeOffset?)) } }; var json = "\"invalid-date\""; @@ -411,7 +411,7 @@ public void DateTimeOffsetNullableConverter_RoundTrip_WithNull_Success() // Arrange var options = new JsonSerializerOptions { - Converters = { new JsonDateTimeConverterAttribute("yyyy-MM-dd").CreateConverter(typeof(DateTimeOffset?)) } + Converters = { new JsonDateTimeConverterAttribute("dd.MM.yyyy").CreateConverter(typeof(DateTimeOffset?)) } }; DateTimeOffset? original = null; @@ -485,7 +485,7 @@ public void DateOnlyConverter_Read_InvalidToken_ThrowsJsonException() // Arrange var options = new JsonSerializerOptions { - Converters = { new JsonDateTimeConverterAttribute("yyyy-MM-dd").CreateConverter(typeof(DateOnly)) } + Converters = { new JsonDateTimeConverterAttribute("dd/MM/yyyy").CreateConverter(typeof(DateOnly)) } }; var json = "\"invalid-date-format\""; @@ -538,7 +538,7 @@ public void DateOnlyNullableConverter_Write_NullValue_Success() // Arrange var options = new JsonSerializerOptions { - Converters = { new JsonDateTimeConverterAttribute("yyyy-MM-dd").CreateConverter(typeof(DateOnly?)) } + Converters = { new JsonDateTimeConverterAttribute("dd-MM-yyyy").CreateConverter(typeof(DateOnly?)) } }; DateOnly? date = null; @@ -573,7 +573,7 @@ public void DateOnlyNullableConverter_Read_NullToken_ReturnsNull() // Arrange var options = new JsonSerializerOptions { - Converters = { new JsonDateTimeConverterAttribute("yyyy-MM-dd").CreateConverter(typeof(DateOnly?)) } + Converters = { new JsonDateTimeConverterAttribute("dd.MM.yyyy").CreateConverter(typeof(DateOnly?)) } }; var json = "null"; @@ -590,7 +590,7 @@ public void DateOnlyNullableConverter_Read_InvalidFormat_ReturnsNull() // Arrange var options = new JsonSerializerOptions { - Converters = { new JsonDateTimeConverterAttribute("yyyy-MM-dd").CreateConverter(typeof(DateOnly?)) } + Converters = { new JsonDateTimeConverterAttribute("MM-dd-yyyy").CreateConverter(typeof(DateOnly?)) } }; var json = "\"invalid-date\""; @@ -625,7 +625,7 @@ public void DateOnlyNullableConverter_RoundTrip_WithNull_Success() // Arrange var options = new JsonSerializerOptions { - Converters = { new JsonDateTimeConverterAttribute("yyyy-MM-dd").CreateConverter(typeof(DateOnly?)) } + Converters = { new JsonDateTimeConverterAttribute("yyyy.MM.dd").CreateConverter(typeof(DateOnly?)) } }; DateOnly? original = null; @@ -681,7 +681,7 @@ public void TimeOnlyConverter_Read_InvalidFormat_FallbackToGetDateTime() // Arrange var options = new JsonSerializerOptions { - Converters = { new JsonDateTimeConverterAttribute("HH:mm").CreateConverter(typeof(TimeOnly)) } + Converters = { new JsonDateTimeConverterAttribute("HH.mm").CreateConverter(typeof(TimeOnly)) } }; // Full datetime that doesn't match our format var json = "\"2023-10-15T14:30:45Z\""; @@ -752,7 +752,7 @@ public void TimeOnlyNullableConverter_Write_NullValue_Success() // Arrange var options = new JsonSerializerOptions { - Converters = { new JsonDateTimeConverterAttribute("HH:mm:ss").CreateConverter(typeof(TimeOnly?)) } + Converters = { new JsonDateTimeConverterAttribute("HH-mm-ss").CreateConverter(typeof(TimeOnly?)) } }; TimeOnly? time = null; @@ -787,7 +787,7 @@ public void TimeOnlyNullableConverter_Read_NullToken_ReturnsNull() // Arrange var options = new JsonSerializerOptions { - Converters = { new JsonDateTimeConverterAttribute("HH:mm:ss").CreateConverter(typeof(TimeOnly?)) } + Converters = { new JsonDateTimeConverterAttribute("HH.mm.ss").CreateConverter(typeof(TimeOnly?)) } }; var json = "null"; @@ -804,7 +804,7 @@ public void TimeOnlyNullableConverter_Read_InvalidFormat_ReturnsNull() // Arrange var options = new JsonSerializerOptions { - Converters = { new JsonDateTimeConverterAttribute("HH:mm:ss").CreateConverter(typeof(TimeOnly?)) } + Converters = { new JsonDateTimeConverterAttribute("mm:ss:HH").CreateConverter(typeof(TimeOnly?)) } }; var json = "\"invalid-time\""; @@ -839,7 +839,7 @@ public void TimeOnlyNullableConverter_RoundTrip_WithNull_Success() // Arrange var options = new JsonSerializerOptions { - Converters = { new JsonDateTimeConverterAttribute("HH:mm:ss").CreateConverter(typeof(TimeOnly?)) } + Converters = { new JsonDateTimeConverterAttribute("HH:mm").CreateConverter(typeof(TimeOnly?)) } }; TimeOnly? original = null; diff --git a/src/Scarlet.System.Text.Json.DateTimeConverter.Tests/DateTimeConverterFactoryHelperTests.cs b/src/Scarlet.System.Text.Json.DateTimeConverter.Tests/DateTimeConverterFactoryHelperTests.cs new file mode 100644 index 0000000..e1db479 --- /dev/null +++ b/src/Scarlet.System.Text.Json.DateTimeConverter.Tests/DateTimeConverterFactoryHelperTests.cs @@ -0,0 +1,131 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Scarlet.System.Text.Json.DateTimeConverter.Tests; + +/// +/// Unit tests for DateTimeConverterFactoryHelper to ensure full code coverage. +/// +public class DateTimeConverterFactoryHelperTests +{ + private const string TestDateFormat = "yyyy-MM-dd"; + private const string TestTimeFormat = "HH:mm:ss"; + + [Fact] + public void CreateConverter_DateTime_ReturnsDateTimeConverter() + { + // Act + var converter = DateTimeConverterFactoryHelper.CreateConverter(typeof(DateTime), TestDateFormat); + + // Assert + Assert.NotNull(converter); + Assert.IsType(converter); + } + + [Fact] + public void CreateConverter_NullableDateTime_ReturnsDateTimeNullableConverter() + { + // Act + var converter = DateTimeConverterFactoryHelper.CreateConverter(typeof(DateTime?), TestDateFormat); + + // Assert + Assert.NotNull(converter); + Assert.IsType(converter); + } + + [Fact] + public void CreateConverter_DateTimeOffset_ReturnsDateTimeOffsetConverter() + { + // Act + var converter = DateTimeConverterFactoryHelper.CreateConverter(typeof(DateTimeOffset), TestDateFormat); + + // Assert + Assert.NotNull(converter); + Assert.IsType(converter); + } + + [Fact] + public void CreateConverter_NullableDateTimeOffset_ReturnsDateTimeOffsetNullableConverter() + { + // Act + var converter = DateTimeConverterFactoryHelper.CreateConverter(typeof(DateTimeOffset?), TestDateFormat); + + // Assert + Assert.NotNull(converter); + Assert.IsType(converter); + } + + [Fact] + public void CreateConverter_DateOnly_ReturnsDateOnlyConverter() + { + // Act + var converter = DateTimeConverterFactoryHelper.CreateConverter(typeof(DateOnly), TestDateFormat); + + // Assert + Assert.NotNull(converter); + Assert.IsType(converter); + } + + [Fact] + public void CreateConverter_NullableDateOnly_ReturnsDateOnlyNullableConverter() + { + // Act + var converter = DateTimeConverterFactoryHelper.CreateConverter(typeof(DateOnly?), TestDateFormat); + + // Assert + Assert.NotNull(converter); + Assert.IsType(converter); + } + + [Fact] + public void CreateConverter_TimeOnly_ReturnsTimeOnlyConverter() + { + // Act + var converter = DateTimeConverterFactoryHelper.CreateConverter(typeof(TimeOnly), TestTimeFormat); + + // Assert + Assert.NotNull(converter); + Assert.IsType(converter); + } + + [Fact] + public void CreateConverter_NullableTimeOnly_ReturnsTimeOnlyNullableConverter() + { + // Act + var converter = DateTimeConverterFactoryHelper.CreateConverter(typeof(TimeOnly?), TestTimeFormat); + + // Assert + Assert.NotNull(converter); + Assert.IsType(converter); + } + + [Fact] + public void CreateConverter_UnsupportedType_ThrowsNotSupportedException() + { + // Act & Assert + var exception = Assert.Throws(() => + DateTimeConverterFactoryHelper.CreateConverter(typeof(string), TestDateFormat)); + + Assert.Contains("System.String", exception.Message); + Assert.Contains("DateTimeConverterFactoryHelper", exception.Message); + } + + [Fact] + public void CreateConverter_AnotherUnsupportedType_ThrowsNotSupportedException() + { + // Act & Assert + var exception = Assert.Throws(() => + DateTimeConverterFactoryHelper.CreateConverter(typeof(int), TestDateFormat)); + + Assert.Contains("System.Int32", exception.Message); + Assert.Contains("DateTimeConverterFactoryHelper", exception.Message); + } + + [Fact] + public void CreateConverter_NullType_ThrowsArgumentNullException() + { + // Act & Assert + Assert.Throws(() => + DateTimeConverterFactoryHelper.CreateConverter(null!, TestDateFormat)); + } +} diff --git a/src/Scarlet.System.Text.Json.DateTimeConverter.Tests/NullableConverterWriteTests.cs b/src/Scarlet.System.Text.Json.DateTimeConverter.Tests/NullableConverterWriteTests.cs new file mode 100644 index 0000000..143dd2c --- /dev/null +++ b/src/Scarlet.System.Text.Json.DateTimeConverter.Tests/NullableConverterWriteTests.cs @@ -0,0 +1,208 @@ +using System.Text; +using System.Text.Json; + +namespace Scarlet.System.Text.Json.DateTimeConverter.Tests; + +/// +/// Direct unit tests for nullable converters' Write method to ensure WriteNullValue() path is covered. +/// These tests directly invoke the converter's Write method with null values. +/// +public class NullableConverterWriteTests +{ + private const string TestDateFormat = "yyyy-MM-dd"; + private const string TestTimeFormat = "HH:mm:ss"; + private const int TestYear = 2023; + private const int TestMonth = 10; + private const int TestDay = 15; + private const int TestHour = 14; + private const int TestMinute = 30; + private const int TestSecond = 45; + + [Fact] + public void DateTimeNullableConverter_Write_DirectCallWithNull_WritesNullValue() + { + // Arrange + var converter = Converters.DateTimeNullableConverter.FromFormat(TestDateFormat); + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream); + DateTime? value = null; + + // Act + converter.Write(writer, value, new JsonSerializerOptions()); + writer.Flush(); + + // Assert + var json = Encoding.UTF8.GetString(stream.ToArray()); + Assert.Equal("null", json); + } + + [Fact] + public void DateTimeNullableConverter_Write_DirectCallWithValue_WritesFormattedDate() + { + // Arrange + var converter = Converters.DateTimeNullableConverter.FromFormat(TestDateFormat); + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream); + DateTime? value = new DateTime(TestYear, TestMonth, TestDay); + + // Act + converter.Write(writer, value, new JsonSerializerOptions()); + writer.Flush(); + + // Assert + var json = Encoding.UTF8.GetString(stream.ToArray()); + Assert.Equal("\"2023-10-15\"", json); + } + + [Fact] + public void DateTimeOffsetNullableConverter_Write_DirectCallWithNull_WritesNullValue() + { + // Arrange + var converter = Converters.DateTimeOffsetNullableConverter.FromFormat(TestDateFormat); + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream); + DateTimeOffset? value = null; + + // Act + converter.Write(writer, value, new JsonSerializerOptions()); + writer.Flush(); + + // Assert + var json = Encoding.UTF8.GetString(stream.ToArray()); + Assert.Equal("null", json); + } + + [Fact] + public void DateTimeOffsetNullableConverter_Write_DirectCallWithValue_WritesFormattedDate() + { + // Arrange + var converter = Converters.DateTimeOffsetNullableConverter.FromFormat(TestDateFormat); + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream); + DateTimeOffset? value = new DateTimeOffset(TestYear, TestMonth, TestDay, 0, 0, 0, TimeSpan.Zero); + + // Act + converter.Write(writer, value, new JsonSerializerOptions()); + writer.Flush(); + + // Assert + var json = Encoding.UTF8.GetString(stream.ToArray()); + Assert.Equal("\"2023-10-15\"", json); + } + + [Fact] + public void DateOnlyNullableConverter_Write_DirectCallWithNull_WritesNullValue() + { + // Arrange + var converter = Converters.DateOnlyNullableConverter.FromFormat(TestDateFormat); + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream); + DateOnly? value = null; + + // Act + converter.Write(writer, value, new JsonSerializerOptions()); + writer.Flush(); + + // Assert + var json = Encoding.UTF8.GetString(stream.ToArray()); + Assert.Equal("null", json); + } + + [Fact] + public void DateOnlyNullableConverter_Write_DirectCallWithValue_WritesFormattedDate() + { + // Arrange + var converter = Converters.DateOnlyNullableConverter.FromFormat(TestDateFormat); + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream); + DateOnly? value = new DateOnly(TestYear, TestMonth, TestDay); + + // Act + converter.Write(writer, value, new JsonSerializerOptions()); + writer.Flush(); + + // Assert + var json = Encoding.UTF8.GetString(stream.ToArray()); + Assert.Equal("\"2023-10-15\"", json); + } + + [Fact] + public void TimeOnlyNullableConverter_Write_DirectCallWithNull_WritesNullValue() + { + // Arrange + var converter = Converters.TimeOnlyNullableConverter.FromFormat(TestTimeFormat); + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream); + TimeOnly? value = null; + + // Act + converter.Write(writer, value, new JsonSerializerOptions()); + writer.Flush(); + + // Assert + var json = Encoding.UTF8.GetString(stream.ToArray()); + Assert.Equal("null", json); + } + + [Fact] + public void TimeOnlyNullableConverter_Write_DirectCallWithValue_WritesFormattedTime() + { + // Arrange + var converter = Converters.TimeOnlyNullableConverter.FromFormat(TestTimeFormat); + using var stream = new MemoryStream(); + using var writer = new Utf8JsonWriter(stream); + TimeOnly? value = new TimeOnly(TestHour, TestMinute, TestSecond); + + // Act + converter.Write(writer, value, new JsonSerializerOptions()); + writer.Flush(); + + // Assert + var json = Encoding.UTF8.GetString(stream.ToArray()); + Assert.Equal("\"14:30:45\"", json); + } + + [Fact] + public void DateTimeNullableConverter_Write_NullWriter_ThrowsArgumentNullException() + { + // Arrange + var converter = Converters.DateTimeNullableConverter.FromFormat(TestDateFormat); + DateTime? value = new DateTime(TestYear, TestMonth, TestDay); + + // Act & Assert + Assert.Throws(() => converter.Write(null!, value, new JsonSerializerOptions())); + } + + [Fact] + public void DateTimeOffsetNullableConverter_Write_NullWriter_ThrowsArgumentNullException() + { + // Arrange + var converter = Converters.DateTimeOffsetNullableConverter.FromFormat(TestDateFormat); + DateTimeOffset? value = new DateTimeOffset(TestYear, TestMonth, TestDay, 0, 0, 0, TimeSpan.Zero); + + // Act & Assert + Assert.Throws(() => converter.Write(null!, value, new JsonSerializerOptions())); + } + + [Fact] + public void DateOnlyNullableConverter_Write_NullWriter_ThrowsArgumentNullException() + { + // Arrange + var converter = Converters.DateOnlyNullableConverter.FromFormat(TestDateFormat); + DateOnly? value = new DateOnly(TestYear, TestMonth, TestDay); + + // Act & Assert + Assert.Throws(() => converter.Write(null!, value, new JsonSerializerOptions())); + } + + [Fact] + public void TimeOnlyNullableConverter_Write_NullWriter_ThrowsArgumentNullException() + { + // Arrange + var converter = Converters.TimeOnlyNullableConverter.FromFormat(TestTimeFormat); + TimeOnly? value = new TimeOnly(TestHour, TestMinute, TestSecond); + + // Act & Assert + Assert.Throws(() => converter.Write(null!, value, new JsonSerializerOptions())); + } +} diff --git a/src/Scarlet.System.Text.Json.DateTimeConverter/Scarlet.System.Text.Json.DateTimeConverter.csproj b/src/Scarlet.System.Text.Json.DateTimeConverter/Scarlet.System.Text.Json.DateTimeConverter.csproj index e2446e4..5f7d9e7 100644 --- a/src/Scarlet.System.Text.Json.DateTimeConverter/Scarlet.System.Text.Json.DateTimeConverter.csproj +++ b/src/Scarlet.System.Text.Json.DateTimeConverter/Scarlet.System.Text.Json.DateTimeConverter.csproj @@ -56,4 +56,8 @@ + + + +