diff --git a/src/Scarlet.System.Text.Json.DateTimeConverter.Tests/DateTimeConverterResolverTests.cs b/src/Scarlet.System.Text.Json.DateTimeConverter.Tests/DateTimeConverterResolverTests.cs new file mode 100644 index 0000000..2bb5d0e --- /dev/null +++ b/src/Scarlet.System.Text.Json.DateTimeConverter.Tests/DateTimeConverterResolverTests.cs @@ -0,0 +1,150 @@ +using System.Text.Json; +using System.Text.Json.Serialization.Metadata; +using Scarlet.System.Text.Json.DateTimeConverter.Tests.Model; + +namespace Scarlet.System.Text.Json.DateTimeConverter.Tests; + +public class DateTimeConverterResolverTests +{ + [Fact] + public void DateTimeConverterResolver_ParameterlessConstructor_CanBeCreated() + { + // Arrange & Act + var resolver = new DateTimeConverterResolver(); + + // Assert + Assert.NotNull(resolver); + } + + [Fact] + public void DateTimeConverterResolver_WithJsonSerializerOptionsConstructor_CanBeCreated() + { + // Arrange + var options = new JsonSerializerOptions(); + + // Act + var resolver = new DateTimeConverterResolver(options); + + // Assert + Assert.NotNull(resolver); + } + + [Fact] + public void DateTimeConverterResolver_ParameterlessConstructor_GetTypeInfo_ReturnsNull() + { + // Arrange + var resolver = new DateTimeConverterResolver(); + + // Act + // This exercises the parameterless constructor and the fallback path in GetTypeInfo(Type) + // since _source is null, it calls GetTypeInfo(type, Options), which returns null + var typeInfo = resolver.GetTypeInfo(typeof(SourceGeneratorModelWithAttribute)); + + // Assert + // Without a source resolver, GetTypeInfo should return null + Assert.Null(typeInfo); + } + + [Fact] + public void DateTimeConverterResolver_WithOptionsConstructor_GetTypeInfo_UsesFallbackPath_ReturnsNull() + { + // Arrange + var options = new JsonSerializerOptions + { + TypeInfoResolver = ResolverModelJsonSerializerContext.Default + }; + // Create a resolver with options - this exercises the JsonSerializerOptions constructor + var resolver = new DateTimeConverterResolver(options); + + // Act + // Call the single-parameter GetTypeInfo + // This will use the fallback path: GetTypeInfo(type, Options) on line 59 + // since _source is null (not a JsonSerializerContext) + // However, GetTypeInfo(Type, JsonSerializerOptions) needs _source to work, so it returns null + var typeInfo = resolver.GetTypeInfo(typeof(SourceGeneratorModelWithAttribute)); + + // Assert + // Without _source set, the resolver can't resolve type info + Assert.Null(typeInfo); + } + + [Fact] + public void DateTimeConverterResolver_GetTypeInfo_WithJsonSerializerContextSource_UsesContextOptions() + { + // Arrange + var sourceContext = ResolverModelJsonSerializerContext.Default; + var resolver = new DateTimeConverterResolver(sourceContext); + + // Act + // This should use the JsonSerializerContext path in GetTypeInfo(Type) + // where it detects _source is JsonSerializerContext and uses its Options + var typeInfo = resolver.GetTypeInfo(typeof(SourceGeneratorModelWithAttribute)); + + // Assert + Assert.NotNull(typeInfo); + Assert.Equal(typeof(SourceGeneratorModelWithAttribute), typeInfo.Type); + } + + [Fact] + public void DateTimeConverterResolver_WithParameterlessConstructor_CanSerializeWithSourceContext() + { + // Arrange + var resolver = new DateTimeConverterResolver(); + var options = new JsonSerializerOptions + { + TypeInfoResolver = JsonTypeInfoResolver.Combine(resolver, ResolverModelJsonSerializerContext.Default), + WriteIndented = true + }; + var model = new SourceGeneratorModelWithAttribute + { + DateTimeProperty = new DateTime(2023, 10, 1, 12, 0, 0, DateTimeKind.Utc), + DateOnlyProperty = new DateOnly(2023, 10, 1) + }; + + // Act + var json = JsonSerializer.Serialize(model, options); + + // Assert + // Should use default formats since resolver has no source with attributes + Assert.Contains("\"DateTimeProperty\": \"2023-10-01T12:00:00Z\"", json); + Assert.Contains("\"DateOnlyProperty\": \"2023-10-01\"", json); + } + + [Fact] + public void DateTimeConverterResolver_WithOptionsConstructor_StoresOptionsForBaseClass() + { + // Arrange + var options = new JsonSerializerOptions + { + WriteIndented = true, + TypeInfoResolver = ResolverModelJsonSerializerContext.Default + }; + // This tests the constructor with JsonSerializerOptions parameter + // The options are passed to the base class JsonSerializerContext + var resolver = new DateTimeConverterResolver(options); + + // Act & Assert + // The resolver is successfully created with the options + // The constructor is used when the resolver is part of framework integration + Assert.NotNull(resolver); + } + + [Fact] + public void DateTimeConverterResolver_WithIJsonTypeInfoResolverSource_UsesSourceForGetTypeInfo() + { + // Arrange + var sourceResolver = ResolverModelJsonSerializerContext.Default; + var resolver = new DateTimeConverterResolver(sourceResolver); + var options = new JsonSerializerOptions(); + + // Act + // Call GetTypeInfo with explicit options - this tests the two-parameter GetTypeInfo + var typeInfo = resolver.GetTypeInfo(typeof(SourceGeneratorModelWithAttribute), options); + + // Assert + // Should get typeInfo from the source resolver + Assert.NotNull(typeInfo); + Assert.Equal(typeof(SourceGeneratorModelWithAttribute), typeInfo.Type); + } +} + diff --git a/src/Scarlet.System.Text.Json.DateTimeConverter.Tests/ReflectionBasedTests.JsonDateTimeFormat.cs b/src/Scarlet.System.Text.Json.DateTimeConverter.Tests/ReflectionBasedTests.JsonDateTimeFormat.cs new file mode 100644 index 0000000..f705a22 --- /dev/null +++ b/src/Scarlet.System.Text.Json.DateTimeConverter.Tests/ReflectionBasedTests.JsonDateTimeFormat.cs @@ -0,0 +1,107 @@ +using System.Text.Json; +using Scarlet.System.Text.Json.DateTimeConverter.Tests.Model; + +namespace Scarlet.System.Text.Json.DateTimeConverter.Tests; + +public partial class ReflectionBasedTests +{ + [Fact] + public void ReflectionBased_WithoutResolver_WithFormatAttribute_DoesNotApplyFormat() + { + // Arrange + var options = new JsonSerializerOptions + { + WriteIndented = true + }; + var originalModel = new SourceGeneratorModelWithAttribute + { + DateTimeProperty = new DateTime(2023, 10, 1, 12, 0, 0, DateTimeKind.Utc), + NullableDateTimeProperty = new DateTime(2023, 10, 1, 12, 0, 0, DateTimeKind.Utc), + DateTimeOffsetProperty = new DateTimeOffset(2023, 10, 1, 12, 0, 0, TimeSpan.Zero), + NullableDateTimeOffsetProperty = new DateTimeOffset(2023, 10, 1, 12, 0, 0, TimeSpan.Zero), + DateOnlyProperty = new DateOnly(2023, 10, 1), + NullableDateOnlyProperty = new DateOnly(2023, 10, 1), + TimeOnlyProperty = new TimeOnly(14, 30, 45), + NullableTimeOnlyProperty = new TimeOnly(14, 30, 45) + }; + // Expected JSON with DEFAULT formats (not the custom formats from JsonDateTimeFormat attribute) + const string expectedJson = """ + { + "DateTimeProperty": "2023-10-01T12:00:00Z", + "NullableDateTimeProperty": "2023-10-01T12:00:00Z", + "DateTimeOffsetProperty": "2023-10-01T12:00:00+00:00", + "NullableDateTimeOffsetProperty": "2023-10-01T12:00:00+00:00", + "DateOnlyProperty": "2023-10-01", + "NullableDateOnlyProperty": "2023-10-01", + "TimeOnlyProperty": "14:30:45", + "NullableTimeOnlyProperty": "14:30:45" + } + """; + + // Act + var json = JsonSerializer.Serialize(originalModel, options); + var deserializedModel = JsonSerializer.Deserialize(json, options); + + // Assert + Assert.NotNull(deserializedModel); + Assert.Equal(originalModel.DateTimeProperty, deserializedModel.DateTimeProperty); + Assert.Equal(originalModel.NullableDateTimeProperty, deserializedModel.NullableDateTimeProperty); + Assert.Equal(originalModel.DateTimeOffsetProperty, deserializedModel.DateTimeOffsetProperty); + Assert.Equal(originalModel.NullableDateTimeOffsetProperty, deserializedModel.NullableDateTimeOffsetProperty); + Assert.Equal(originalModel.DateOnlyProperty, deserializedModel.DateOnlyProperty); + Assert.Equal(originalModel.NullableDateOnlyProperty, deserializedModel.NullableDateOnlyProperty); + Assert.Equal(originalModel.TimeOnlyProperty, deserializedModel.TimeOnlyProperty); + Assert.Equal(originalModel.NullableTimeOnlyProperty, deserializedModel.NullableTimeOnlyProperty); + Assert.Equal(expectedJson, json); + } + + [Fact] + public void ReflectionBased_WithoutResolver_WithFormatAttribute_WithNullValues_DoesNotApplyFormat() + { + // Arrange + var options = new JsonSerializerOptions + { + WriteIndented = true + }; + var originalModel = new SourceGeneratorModelWithAttribute + { + DateTimeProperty = new DateTime(2023, 10, 1, 12, 0, 0, DateTimeKind.Utc), + NullableDateTimeProperty = null, + DateTimeOffsetProperty = new DateTimeOffset(2023, 10, 1, 12, 0, 0, TimeSpan.Zero), + NullableDateTimeOffsetProperty = null, + DateOnlyProperty = new DateOnly(2023, 10, 1), + NullableDateOnlyProperty = null, + TimeOnlyProperty = new TimeOnly(14, 30, 45), + NullableTimeOnlyProperty = null + }; + // Expected JSON with DEFAULT formats (not the custom formats from JsonDateTimeFormat attribute) + const string expectedJson = """ + { + "DateTimeProperty": "2023-10-01T12:00:00Z", + "NullableDateTimeProperty": null, + "DateTimeOffsetProperty": "2023-10-01T12:00:00+00:00", + "NullableDateTimeOffsetProperty": null, + "DateOnlyProperty": "2023-10-01", + "NullableDateOnlyProperty": null, + "TimeOnlyProperty": "14:30:45", + "NullableTimeOnlyProperty": null + } + """; + + // Act + var json = JsonSerializer.Serialize(originalModel, options); + var deserializedModel = JsonSerializer.Deserialize(json, options); + + // Assert + Assert.NotNull(deserializedModel); + Assert.Equal(originalModel.DateTimeProperty, deserializedModel.DateTimeProperty); + Assert.Null(deserializedModel.NullableDateTimeProperty); + Assert.Equal(originalModel.DateTimeOffsetProperty, deserializedModel.DateTimeOffsetProperty); + Assert.Null(deserializedModel.NullableDateTimeOffsetProperty); + Assert.Equal(originalModel.DateOnlyProperty, deserializedModel.DateOnlyProperty); + Assert.Null(deserializedModel.NullableDateOnlyProperty); + Assert.Equal(originalModel.TimeOnlyProperty, deserializedModel.TimeOnlyProperty); + Assert.Null(deserializedModel.NullableTimeOnlyProperty); + Assert.Equal(expectedJson, json); + } +} diff --git a/src/Scarlet.System.Text.Json.DateTimeConverter.Tests/SourceGeneratorBasedTests.JsonDateTimeFormat.cs b/src/Scarlet.System.Text.Json.DateTimeConverter.Tests/SourceGeneratorBasedTests.JsonDateTimeFormat.cs index 03cf6dc..a7f22de 100644 --- a/src/Scarlet.System.Text.Json.DateTimeConverter.Tests/SourceGeneratorBasedTests.JsonDateTimeFormat.cs +++ b/src/Scarlet.System.Text.Json.DateTimeConverter.Tests/SourceGeneratorBasedTests.JsonDateTimeFormat.cs @@ -151,4 +151,154 @@ public void SourceGenerator_WithResolver_WithFormatAttribute_WithNullValues_Usin Assert.Null(deserializedModel.NullableTimeOnlyProperty); Assert.Equal(expectedJson, json); } + + [Fact] + public void SourceGenerator_WithoutResolver_WithFormatAttribute_DoesNotApplyFormat_UsingOptions() + { + // Arrange + var sourceGenOptions = new JsonSerializerOptions + { + WriteIndented = true, + TypeInfoResolver = ResolverModelJsonSerializerContext.Default + }; + var originalModel = new SourceGeneratorModelWithAttribute + { + DateTimeProperty = new DateTime(2023, 10, 1, 12, 0, 0, DateTimeKind.Utc), + NullableDateTimeProperty = new DateTime(2023, 10, 1, 12, 0, 0, DateTimeKind.Utc), + DateTimeOffsetProperty = new DateTimeOffset(2023, 10, 1, 12, 0, 0, TimeSpan.Zero), + NullableDateTimeOffsetProperty = new DateTimeOffset(2023, 10, 1, 12, 0, 0, TimeSpan.Zero), + DateOnlyProperty = new DateOnly(2023, 10, 1), + NullableDateOnlyProperty = new DateOnly(2023, 10, 1), + TimeOnlyProperty = new TimeOnly(14, 30, 45), + NullableTimeOnlyProperty = new TimeOnly(14, 30, 45) + }; + // Expected JSON with DEFAULT formats (not the custom formats from JsonDateTimeFormat attribute) + const string expectedJson = """ + { + "DateTimeProperty": "2023-10-01T12:00:00Z", + "NullableDateTimeProperty": "2023-10-01T12:00:00Z", + "DateTimeOffsetProperty": "2023-10-01T12:00:00+00:00", + "NullableDateTimeOffsetProperty": "2023-10-01T12:00:00+00:00", + "DateOnlyProperty": "2023-10-01", + "NullableDateOnlyProperty": "2023-10-01", + "TimeOnlyProperty": "14:30:45", + "NullableTimeOnlyProperty": "14:30:45" + } + """; + + // Act + var json = JsonSerializer.Serialize(originalModel, sourceGenOptions); + var deserializedModel = JsonSerializer.Deserialize(json, sourceGenOptions); + + // Assert + Assert.NotNull(deserializedModel); + Assert.Equal(originalModel.DateTimeProperty, deserializedModel.DateTimeProperty); + Assert.Equal(originalModel.NullableDateTimeProperty, deserializedModel.NullableDateTimeProperty); + Assert.Equal(originalModel.DateTimeOffsetProperty, deserializedModel.DateTimeOffsetProperty); + Assert.Equal(originalModel.NullableDateTimeOffsetProperty, deserializedModel.NullableDateTimeOffsetProperty); + Assert.Equal(originalModel.DateOnlyProperty, deserializedModel.DateOnlyProperty); + Assert.Equal(originalModel.NullableDateOnlyProperty, deserializedModel.NullableDateOnlyProperty); + Assert.Equal(originalModel.TimeOnlyProperty, deserializedModel.TimeOnlyProperty); + Assert.Equal(originalModel.NullableTimeOnlyProperty, deserializedModel.NullableTimeOnlyProperty); + Assert.Equal(expectedJson, json); + } + + [Fact] + public void SourceGenerator_WithoutResolver_WithFormatAttribute_WithNullValues_DoesNotApplyFormat_UsingOptions() + { + // Arrange + var sourceGenOptions = new JsonSerializerOptions + { + WriteIndented = true, + TypeInfoResolver = ResolverModelJsonSerializerContext.Default + }; + var originalModel = new SourceGeneratorModelWithAttribute + { + DateTimeProperty = new DateTime(2023, 10, 1, 12, 0, 0, DateTimeKind.Utc), + NullableDateTimeProperty = null, + DateTimeOffsetProperty = new DateTimeOffset(2023, 10, 1, 12, 0, 0, TimeSpan.Zero), + NullableDateTimeOffsetProperty = null, + DateOnlyProperty = new DateOnly(2023, 10, 1), + NullableDateOnlyProperty = null, + TimeOnlyProperty = new TimeOnly(14, 30, 45), + NullableTimeOnlyProperty = null + }; + // Expected JSON with DEFAULT formats (not the custom formats from JsonDateTimeFormat attribute) + const string expectedJson = """ + { + "DateTimeProperty": "2023-10-01T12:00:00Z", + "NullableDateTimeProperty": null, + "DateTimeOffsetProperty": "2023-10-01T12:00:00+00:00", + "NullableDateTimeOffsetProperty": null, + "DateOnlyProperty": "2023-10-01", + "NullableDateOnlyProperty": null, + "TimeOnlyProperty": "14:30:45", + "NullableTimeOnlyProperty": null + } + """; + + // Act + var json = JsonSerializer.Serialize(originalModel, sourceGenOptions); + var deserializedModel = JsonSerializer.Deserialize(json, sourceGenOptions); + + // Assert + Assert.NotNull(deserializedModel); + Assert.Equal(originalModel.DateTimeProperty, deserializedModel.DateTimeProperty); + Assert.Null(deserializedModel.NullableDateTimeProperty); + Assert.Equal(originalModel.DateTimeOffsetProperty, deserializedModel.DateTimeOffsetProperty); + Assert.Null(deserializedModel.NullableDateTimeOffsetProperty); + Assert.Equal(originalModel.DateOnlyProperty, deserializedModel.DateOnlyProperty); + Assert.Null(deserializedModel.NullableDateOnlyProperty); + Assert.Equal(originalModel.TimeOnlyProperty, deserializedModel.TimeOnlyProperty); + Assert.Null(deserializedModel.NullableTimeOnlyProperty); + Assert.Equal(expectedJson, json); + } + + [Fact] + public void SourceGenerator_WithoutResolver_WithFormatAttribute_DoesNotApplyFormat_UsingContext() + { + // Arrange + var testModelType = typeof(SourceGeneratorModelWithAttribute); + var context = ResolverModelJsonSerializerContext.Default; + var originalModel = new SourceGeneratorModelWithAttribute + { + DateTimeProperty = new DateTime(2023, 10, 1, 12, 0, 0, DateTimeKind.Utc), + NullableDateTimeProperty = new DateTime(2023, 10, 1, 12, 0, 0, DateTimeKind.Utc), + DateTimeOffsetProperty = new DateTimeOffset(2023, 10, 1, 12, 0, 0, TimeSpan.Zero), + NullableDateTimeOffsetProperty = new DateTimeOffset(2023, 10, 1, 12, 0, 0, TimeSpan.Zero), + DateOnlyProperty = new DateOnly(2023, 10, 1), + NullableDateOnlyProperty = new DateOnly(2023, 10, 1), + TimeOnlyProperty = new TimeOnly(14, 30, 45), + NullableTimeOnlyProperty = new TimeOnly(14, 30, 45) + }; + // Expected JSON with DEFAULT formats (not the custom formats from JsonDateTimeFormat attribute) + const string expectedJson = """ + { + "DateTimeProperty": "2023-10-01T12:00:00Z", + "NullableDateTimeProperty": "2023-10-01T12:00:00Z", + "DateTimeOffsetProperty": "2023-10-01T12:00:00+00:00", + "NullableDateTimeOffsetProperty": "2023-10-01T12:00:00+00:00", + "DateOnlyProperty": "2023-10-01", + "NullableDateOnlyProperty": "2023-10-01", + "TimeOnlyProperty": "14:30:45", + "NullableTimeOnlyProperty": "14:30:45" + } + """; + + // Act + var json = JsonSerializer.Serialize(originalModel, testModelType, context); + var deserializedModel = (SourceGeneratorModelWithAttribute?)JsonSerializer.Deserialize(json, testModelType, context); + + // Assert + Assert.NotNull(deserializedModel); + Assert.Equal(originalModel.DateTimeProperty, deserializedModel.DateTimeProperty); + Assert.Equal(originalModel.NullableDateTimeProperty, deserializedModel.NullableDateTimeProperty); + Assert.Equal(originalModel.DateTimeOffsetProperty, deserializedModel.DateTimeOffsetProperty); + Assert.Equal(originalModel.NullableDateTimeOffsetProperty, deserializedModel.NullableDateTimeOffsetProperty); + Assert.Equal(originalModel.DateOnlyProperty, deserializedModel.DateOnlyProperty); + Assert.Equal(originalModel.NullableDateOnlyProperty, deserializedModel.NullableDateOnlyProperty); + Assert.Equal(originalModel.TimeOnlyProperty, deserializedModel.TimeOnlyProperty); + Assert.Equal(originalModel.NullableTimeOnlyProperty, deserializedModel.NullableTimeOnlyProperty); + Assert.Equal(expectedJson, json); + } } \ No newline at end of file