From 4ef500b8e82f54e24701addd0e4a592f9a51049d Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Tue, 7 Feb 2023 12:59:16 +0100 Subject: [PATCH 1/2] Update to build against JsonApiDotNetCore v5.1.2 --- Directory.Build.props | 2 +- .../ServiceCollectionExtensions.cs | 2 - .../MongoQueryableBuilder.cs | 39 ---- .../MongoWhereClauseBuilder.cs | 46 ----- .../Repositories/MongoRepository.cs | 5 +- .../Filtering/FilterDataTypeTests.cs | 39 +++- .../Filtering/FilterOperatorTests.cs | 194 ++++++++++++++---- .../Filtering/FilterableResource.cs | 4 + 8 files changed, 200 insertions(+), 131 deletions(-) delete mode 100644 src/JsonApiDotNetCore.MongoDb/Queries/Internal/QueryableBuilding/MongoQueryableBuilder.cs delete mode 100644 src/JsonApiDotNetCore.MongoDb/Queries/Internal/QueryableBuilding/MongoWhereClauseBuilder.cs diff --git a/Directory.Build.props b/Directory.Build.props index 6915131..a11f520 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -2,7 +2,7 @@ net6.0 6.0.* - 5.1.0 + 5.1.2 2.15.0 5.1.0 $(MSBuildThisFileDirectory)CodingGuidelines.ruleset diff --git a/src/JsonApiDotNetCore.MongoDb/Configuration/ServiceCollectionExtensions.cs b/src/JsonApiDotNetCore.MongoDb/Configuration/ServiceCollectionExtensions.cs index 8e6597e..f914f91 100644 --- a/src/JsonApiDotNetCore.MongoDb/Configuration/ServiceCollectionExtensions.cs +++ b/src/JsonApiDotNetCore.MongoDb/Configuration/ServiceCollectionExtensions.cs @@ -6,8 +6,6 @@ using JsonApiDotNetCore.Queries.Internal; using Microsoft.Extensions.DependencyInjection; -#pragma warning disable AV1130 // Return type in method signature should be an interface to an unchangeable collection - namespace JsonApiDotNetCore.MongoDb.Configuration; public static class ServiceCollectionExtensions diff --git a/src/JsonApiDotNetCore.MongoDb/Queries/Internal/QueryableBuilding/MongoQueryableBuilder.cs b/src/JsonApiDotNetCore.MongoDb/Queries/Internal/QueryableBuilding/MongoQueryableBuilder.cs deleted file mode 100644 index 87f4715..0000000 --- a/src/JsonApiDotNetCore.MongoDb/Queries/Internal/QueryableBuilding/MongoQueryableBuilder.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.Linq.Expressions; -using JetBrains.Annotations; -using JsonApiDotNetCore.Queries; -using JsonApiDotNetCore.Queries.Expressions; -using JsonApiDotNetCore.Queries.Internal.QueryableBuilding; -using JsonApiDotNetCore.Resources; -using Microsoft.EntityFrameworkCore.Metadata; - -namespace JsonApiDotNetCore.MongoDb.Queries.Internal.QueryableBuilding; - -/// -/// Drives conversion from into system trees. -/// -[PublicAPI] -public sealed class MongoQueryableBuilder : QueryableBuilder -{ - private readonly Type _elementType; - private readonly Type _extensionType; - private readonly LambdaParameterNameFactory _nameFactory; - private readonly LambdaScopeFactory _lambdaScopeFactory; - - public MongoQueryableBuilder(Expression source, Type elementType, Type extensionType, LambdaParameterNameFactory nameFactory, - IResourceFactory resourceFactory, IModel entityModel, LambdaScopeFactory? lambdaScopeFactory = null) - : base(source, elementType, extensionType, nameFactory, resourceFactory, entityModel, lambdaScopeFactory) - { - _elementType = elementType; - _extensionType = extensionType; - _nameFactory = nameFactory; - _lambdaScopeFactory = lambdaScopeFactory ?? new LambdaScopeFactory(nameFactory); - } - - protected override Expression ApplyFilter(Expression source, FilterExpression filter) - { - using LambdaScope lambdaScope = _lambdaScopeFactory.CreateScope(_elementType); - - var builder = new MongoWhereClauseBuilder(source, lambdaScope, _extensionType, _nameFactory); - return builder.ApplyWhere(filter); - } -} diff --git a/src/JsonApiDotNetCore.MongoDb/Queries/Internal/QueryableBuilding/MongoWhereClauseBuilder.cs b/src/JsonApiDotNetCore.MongoDb/Queries/Internal/QueryableBuilding/MongoWhereClauseBuilder.cs deleted file mode 100644 index fef7758..0000000 --- a/src/JsonApiDotNetCore.MongoDb/Queries/Internal/QueryableBuilding/MongoWhereClauseBuilder.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Linq.Expressions; -using JetBrains.Annotations; -using JsonApiDotNetCore.Queries.Expressions; -using JsonApiDotNetCore.Queries.Internal.QueryableBuilding; - -namespace JsonApiDotNetCore.MongoDb.Queries.Internal.QueryableBuilding; - -/// -[PublicAPI] -public class MongoWhereClauseBuilder : WhereClauseBuilder -{ - public MongoWhereClauseBuilder(Expression source, LambdaScope lambdaScope, Type extensionType, LambdaParameterNameFactory nameFactory) - : base(source, lambdaScope, extensionType, nameFactory) - { - } - - public override Expression VisitLiteralConstant(LiteralConstantExpression expression, Type? expressionType) - { - if (expressionType == typeof(DateTime) || expressionType == typeof(DateTime?)) - { - DateTime? dateTime = TryParseDateTimeAsUtc(expression.Value, expressionType); - return Expression.Constant(dateTime); - } - - return base.VisitLiteralConstant(expression, expressionType); - } - - private static DateTime? TryParseDateTimeAsUtc(string value, Type expressionType) - { - object convertedValue = Convert.ChangeType(value, expressionType); - - if (convertedValue is DateTime dateTime) - { - // DateTime values in MongoDB are always stored in UTC, so any ambiguous filter value passed - // must be interpreted as such for correct comparison. - if (dateTime.Kind == DateTimeKind.Unspecified) - { - return DateTime.SpecifyKind(dateTime, DateTimeKind.Utc); - } - - return dateTime; - } - - return null; - } -} diff --git a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoRepository.cs b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoRepository.cs index e812a9e..6bd64e3 100644 --- a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoRepository.cs +++ b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoRepository.cs @@ -4,7 +4,6 @@ using JsonApiDotNetCore.Errors; using JsonApiDotNetCore.Middleware; using JsonApiDotNetCore.MongoDb.Errors; -using JsonApiDotNetCore.MongoDb.Queries.Internal.QueryableBuilding; using JsonApiDotNetCore.MongoDb.Resources; using JsonApiDotNetCore.Queries; using JsonApiDotNetCore.Queries.Expressions; @@ -115,16 +114,14 @@ protected virtual IMongoQueryable ApplyQueryLayer(QueryLayer queryLay var nameFactory = new LambdaParameterNameFactory(); - var builder = new MongoQueryableBuilder(source.Expression, source.ElementType, typeof(Queryable), nameFactory, _resourceFactory, + var builder = new QueryableBuilder(source.Expression, source.ElementType, typeof(Queryable), nameFactory, _resourceFactory, new MongoModel(_resourceGraph)); Expression expression = builder.ApplyQuery(queryLayer); return (IMongoQueryable)source.Provider.CreateQuery(expression); } -#pragma warning disable AV1130 // Return type in method signature should be an interface to an unchangeable collection protected virtual IQueryable GetAll() -#pragma warning restore AV1130 // Return type in method signature should be an interface to an unchangeable collection { return _mongoDataAccess.ActiveSession != null ? Collection.AsQueryable(_mongoDataAccess.ActiveSession) : Collection.AsQueryable(); } diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Filtering/FilterDataTypeTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Filtering/FilterDataTypeTests.cs index 83a5eb7..3e97ce3 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Filtering/FilterDataTypeTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Filtering/FilterDataTypeTests.cs @@ -1,3 +1,4 @@ +using System.Globalization; using System.Net; using System.Reflection; using System.Web; @@ -49,7 +50,9 @@ await _testContext.RunOnDatabaseAsync(async dbContext => }); string attributeName = propertyName.Camelize(); - string route = $"/filterableResources?filter=equals({attributeName},'{propertyValue}')"; + string? attributeValue = Convert.ToString(propertyValue, CultureInfo.InvariantCulture); + + string route = $"/filterableResources?filter=equals({attributeName},'{attributeValue}')"; // Act (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); @@ -77,7 +80,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - string route = $"/filterableResources?filter=equals(someDecimal,'{resource.SomeDecimal}')"; + string route = $"/filterableResources?filter=equals(someDecimal,'{resource.SomeDecimal.ToString(CultureInfo.InvariantCulture)}')"; // Act (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); @@ -117,6 +120,36 @@ await _testContext.RunOnDatabaseAsync(async dbContext => responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey("someGuid").With(value => value.Should().Be(resource.SomeGuid)); } + [Fact] + public async Task Can_filter_equality_on_type_DateTime_in_local_time_zone() + { + // Arrange + var resource = new FilterableResource + { + SomeDateTimeInLocalZone = 27.January(2003).At(11, 22, 33, 44) + }; + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + await dbContext.ClearTableAsync(); + dbContext.FilterableResources.AddRange(resource, new FilterableResource()); + await dbContext.SaveChangesAsync(); + }); + + string route = $"/filterableResources?filter=equals(someDateTimeInLocalZone,'{resource.SomeDateTimeInLocalZone:O}')"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); + + responseDocument.Data.ManyValue.ShouldHaveCount(1); + + responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey("someDateTimeInLocalZone") + .With(value => value.Should().Be(resource.SomeDateTimeInLocalZone)); + } + [Fact] public async Task Can_filter_equality_on_type_DateTime_in_UTC_time_zone() { @@ -191,7 +224,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - string route = $"/filterableResources?filter=equals(someTimeSpan,'{resource.SomeTimeSpan}')"; + string route = $"/filterableResources?filter=equals(someTimeSpan,'{resource.SomeTimeSpan:c}')"; // Act (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Filtering/FilterOperatorTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Filtering/FilterOperatorTests.cs index 923a28b..60e10e7 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Filtering/FilterOperatorTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Filtering/FilterOperatorTests.cs @@ -13,6 +13,26 @@ namespace JsonApiDotNetCoreMongoDbTests.IntegrationTests.QueryStrings.Filtering; public sealed class FilterOperatorTests : IClassFixture> { + private const string IntLowerBound = "19"; + private const string IntInTheRange = "20"; + private const string IntUpperBound = "21"; + + private const string DoubleLowerBound = "1.9"; + private const string DoubleInTheRange = "2.0"; + private const string DoubleUpperBound = "2.1"; + + private const string IsoDateTimeLowerBound = "2000-11-22T09:48:17"; + private const string IsoDateTimeInTheRange = "2000-11-22T12:34:56"; + private const string IsoDateTimeUpperBound = "2000-11-22T18:47:32"; + + private const string InvariantDateTimeLowerBound = "11/22/2000 9:48:17"; + private const string InvariantDateTimeInTheRange = "11/22/2000 12:34:56"; + private const string InvariantDateTimeUpperBound = "11/22/2000 18:47:32"; + + private const string TimeSpanLowerBound = "2:15:28:54.997"; + private const string TimeSpanInTheRange = "2:15:51:42.397"; + private const string TimeSpanUpperBound = "2:16:22:41.736"; + private readonly IntegrationTestContext _testContext; public FilterOperatorTests(IntegrationTestContext testContext) @@ -71,25 +91,26 @@ public async Task Cannot_filter_equality_on_two_attributes() } [Theory] - [InlineData(19, 21, ComparisonOperator.LessThan, 20)] - [InlineData(19, 21, ComparisonOperator.LessThan, 21)] - [InlineData(19, 21, ComparisonOperator.LessOrEqual, 20)] - [InlineData(19, 21, ComparisonOperator.LessOrEqual, 19)] - [InlineData(21, 19, ComparisonOperator.GreaterThan, 20)] - [InlineData(21, 19, ComparisonOperator.GreaterThan, 19)] - [InlineData(21, 19, ComparisonOperator.GreaterOrEqual, 20)] - [InlineData(21, 19, ComparisonOperator.GreaterOrEqual, 21)] - public async Task Can_filter_comparison_on_whole_number(int matchingValue, int nonMatchingValue, ComparisonOperator filterOperator, double filterValue) + [InlineData(IntLowerBound, IntUpperBound, ComparisonOperator.LessThan, IntInTheRange)] + [InlineData(IntLowerBound, IntUpperBound, ComparisonOperator.LessThan, IntUpperBound)] + [InlineData(IntLowerBound, IntUpperBound, ComparisonOperator.LessOrEqual, IntInTheRange)] + [InlineData(IntLowerBound, IntUpperBound, ComparisonOperator.LessOrEqual, IntLowerBound)] + [InlineData(IntUpperBound, IntLowerBound, ComparisonOperator.GreaterThan, IntInTheRange)] + [InlineData(IntUpperBound, IntLowerBound, ComparisonOperator.GreaterThan, IntLowerBound)] + [InlineData(IntUpperBound, IntLowerBound, ComparisonOperator.GreaterOrEqual, IntInTheRange)] + [InlineData(IntUpperBound, IntLowerBound, ComparisonOperator.GreaterOrEqual, IntUpperBound)] + public async Task Can_filter_comparison_on_whole_number(string matchingValue, string nonMatchingValue, ComparisonOperator filterOperator, + string filterValue) { // Arrange var resource = new FilterableResource { - SomeInt32 = matchingValue + SomeInt32 = int.Parse(matchingValue, CultureInfo.InvariantCulture) }; var otherResource = new FilterableResource { - SomeInt32 = nonMatchingValue + SomeInt32 = int.Parse(nonMatchingValue, CultureInfo.InvariantCulture) }; await _testContext.RunOnDatabaseAsync(async dbContext => @@ -112,26 +133,26 @@ await _testContext.RunOnDatabaseAsync(async dbContext => } [Theory] - [InlineData(1.9, 2.1, ComparisonOperator.LessThan, 2.0)] - [InlineData(1.9, 2.1, ComparisonOperator.LessThan, 2.1)] - [InlineData(1.9, 2.1, ComparisonOperator.LessOrEqual, 2.0)] - [InlineData(1.9, 2.1, ComparisonOperator.LessOrEqual, 1.9)] - [InlineData(2.1, 1.9, ComparisonOperator.GreaterThan, 2.0)] - [InlineData(2.1, 1.9, ComparisonOperator.GreaterThan, 1.9)] - [InlineData(2.1, 1.9, ComparisonOperator.GreaterOrEqual, 2.0)] - [InlineData(2.1, 1.9, ComparisonOperator.GreaterOrEqual, 2.1)] - public async Task Can_filter_comparison_on_fractional_number(double matchingValue, double nonMatchingValue, ComparisonOperator filterOperator, - double filterValue) + [InlineData(DoubleLowerBound, DoubleUpperBound, ComparisonOperator.LessThan, DoubleInTheRange)] + [InlineData(DoubleLowerBound, DoubleUpperBound, ComparisonOperator.LessThan, DoubleUpperBound)] + [InlineData(DoubleLowerBound, DoubleUpperBound, ComparisonOperator.LessOrEqual, DoubleInTheRange)] + [InlineData(DoubleLowerBound, DoubleUpperBound, ComparisonOperator.LessOrEqual, DoubleLowerBound)] + [InlineData(DoubleUpperBound, DoubleLowerBound, ComparisonOperator.GreaterThan, DoubleInTheRange)] + [InlineData(DoubleUpperBound, DoubleLowerBound, ComparisonOperator.GreaterThan, DoubleLowerBound)] + [InlineData(DoubleUpperBound, DoubleLowerBound, ComparisonOperator.GreaterOrEqual, DoubleInTheRange)] + [InlineData(DoubleUpperBound, DoubleLowerBound, ComparisonOperator.GreaterOrEqual, DoubleUpperBound)] + public async Task Can_filter_comparison_on_fractional_number(string matchingValue, string nonMatchingValue, ComparisonOperator filterOperator, + string filterValue) { // Arrange var resource = new FilterableResource { - SomeDouble = matchingValue + SomeDouble = double.Parse(matchingValue, CultureInfo.InvariantCulture) }; var otherResource = new FilterableResource { - SomeDouble = nonMatchingValue + SomeDouble = double.Parse(nonMatchingValue, CultureInfo.InvariantCulture) }; await _testContext.RunOnDatabaseAsync(async dbContext => @@ -154,26 +175,34 @@ await _testContext.RunOnDatabaseAsync(async dbContext => } [Theory] - [InlineData("2001-01-01", "2001-01-09", ComparisonOperator.LessThan, "2001-01-05Z")] - [InlineData("2001-01-01", "2001-01-09", ComparisonOperator.LessThan, "2001-01-09Z")] - [InlineData("2001-01-01", "2001-01-09", ComparisonOperator.LessOrEqual, "2001-01-05Z")] - [InlineData("2001-01-01", "2001-01-09", ComparisonOperator.LessOrEqual, "2001-01-01Z")] - [InlineData("2001-01-09", "2001-01-01", ComparisonOperator.GreaterThan, "2001-01-05Z")] - [InlineData("2001-01-09", "2001-01-01", ComparisonOperator.GreaterThan, "2001-01-01Z")] - [InlineData("2001-01-09", "2001-01-01", ComparisonOperator.GreaterOrEqual, "2001-01-05Z")] - [InlineData("2001-01-09", "2001-01-01", ComparisonOperator.GreaterOrEqual, "2001-01-09Z")] - public async Task Can_filter_comparison_on_DateTime_in_UTC_time_zone(string matchingDateTime, string nonMatchingDateTime, ComparisonOperator filterOperator, - string filterDateTime) + [InlineData(IsoDateTimeLowerBound, IsoDateTimeUpperBound, ComparisonOperator.LessThan, IsoDateTimeInTheRange)] + [InlineData(IsoDateTimeLowerBound, IsoDateTimeUpperBound, ComparisonOperator.LessThan, IsoDateTimeUpperBound)] + [InlineData(IsoDateTimeLowerBound, IsoDateTimeUpperBound, ComparisonOperator.LessOrEqual, IsoDateTimeInTheRange)] + [InlineData(IsoDateTimeLowerBound, IsoDateTimeUpperBound, ComparisonOperator.LessOrEqual, IsoDateTimeLowerBound)] + [InlineData(IsoDateTimeUpperBound, IsoDateTimeLowerBound, ComparisonOperator.GreaterThan, IsoDateTimeInTheRange)] + [InlineData(IsoDateTimeUpperBound, IsoDateTimeLowerBound, ComparisonOperator.GreaterThan, IsoDateTimeLowerBound)] + [InlineData(IsoDateTimeUpperBound, IsoDateTimeLowerBound, ComparisonOperator.GreaterOrEqual, IsoDateTimeInTheRange)] + [InlineData(IsoDateTimeUpperBound, IsoDateTimeLowerBound, ComparisonOperator.GreaterOrEqual, IsoDateTimeUpperBound)] + [InlineData(InvariantDateTimeLowerBound, InvariantDateTimeUpperBound, ComparisonOperator.LessThan, InvariantDateTimeInTheRange)] + [InlineData(InvariantDateTimeLowerBound, InvariantDateTimeUpperBound, ComparisonOperator.LessThan, InvariantDateTimeUpperBound)] + [InlineData(InvariantDateTimeLowerBound, InvariantDateTimeUpperBound, ComparisonOperator.LessOrEqual, InvariantDateTimeInTheRange)] + [InlineData(InvariantDateTimeLowerBound, InvariantDateTimeUpperBound, ComparisonOperator.LessOrEqual, InvariantDateTimeLowerBound)] + [InlineData(InvariantDateTimeUpperBound, InvariantDateTimeLowerBound, ComparisonOperator.GreaterThan, InvariantDateTimeInTheRange)] + [InlineData(InvariantDateTimeUpperBound, InvariantDateTimeLowerBound, ComparisonOperator.GreaterThan, InvariantDateTimeLowerBound)] + [InlineData(InvariantDateTimeUpperBound, InvariantDateTimeLowerBound, ComparisonOperator.GreaterOrEqual, InvariantDateTimeInTheRange)] + [InlineData(InvariantDateTimeUpperBound, InvariantDateTimeLowerBound, ComparisonOperator.GreaterOrEqual, InvariantDateTimeUpperBound)] + public async Task Can_filter_comparison_on_DateTime_in_UTC_time_zone(string matchingValue, string nonMatchingValue, ComparisonOperator filterOperator, + string filterValue) { // Arrange var resource = new FilterableResource { - SomeDateTimeInUtcZone = DateTime.Parse(matchingDateTime, CultureInfo.InvariantCulture).AsUtc() + SomeDateTimeInUtcZone = DateTime.Parse(matchingValue, CultureInfo.InvariantCulture).AsUtc() }; var otherResource = new FilterableResource { - SomeDateTimeInUtcZone = DateTime.Parse(nonMatchingDateTime, CultureInfo.InvariantCulture).AsUtc() + SomeDateTimeInUtcZone = DateTime.Parse(nonMatchingValue, CultureInfo.InvariantCulture).AsUtc() }; await _testContext.RunOnDatabaseAsync(async dbContext => @@ -183,7 +212,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - string route = $"/filterableResources?filter={filterOperator.ToString().Camelize()}(someDateTimeInUtcZone,'{filterDateTime}')"; + string route = $"/filterableResources?filter={filterOperator.ToString().Camelize()}(someDateTimeInUtcZone,'{filterValue}Z')"; // Act (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); @@ -197,6 +226,98 @@ await _testContext.RunOnDatabaseAsync(async dbContext => .With(value => value.Should().Be(resource.SomeDateTimeInUtcZone)); } + [Theory] + [InlineData(IsoDateTimeLowerBound, IsoDateTimeUpperBound, ComparisonOperator.LessThan, IsoDateTimeInTheRange)] + [InlineData(IsoDateTimeLowerBound, IsoDateTimeUpperBound, ComparisonOperator.LessThan, IsoDateTimeUpperBound)] + [InlineData(IsoDateTimeLowerBound, IsoDateTimeUpperBound, ComparisonOperator.LessOrEqual, IsoDateTimeInTheRange)] + [InlineData(IsoDateTimeLowerBound, IsoDateTimeUpperBound, ComparisonOperator.LessOrEqual, IsoDateTimeLowerBound)] + [InlineData(IsoDateTimeUpperBound, IsoDateTimeLowerBound, ComparisonOperator.GreaterThan, IsoDateTimeInTheRange)] + [InlineData(IsoDateTimeUpperBound, IsoDateTimeLowerBound, ComparisonOperator.GreaterThan, IsoDateTimeLowerBound)] + [InlineData(IsoDateTimeUpperBound, IsoDateTimeLowerBound, ComparisonOperator.GreaterOrEqual, IsoDateTimeInTheRange)] + [InlineData(IsoDateTimeUpperBound, IsoDateTimeLowerBound, ComparisonOperator.GreaterOrEqual, IsoDateTimeUpperBound)] + [InlineData(InvariantDateTimeLowerBound, InvariantDateTimeUpperBound, ComparisonOperator.LessThan, InvariantDateTimeInTheRange)] + [InlineData(InvariantDateTimeLowerBound, InvariantDateTimeUpperBound, ComparisonOperator.LessThan, InvariantDateTimeUpperBound)] + [InlineData(InvariantDateTimeLowerBound, InvariantDateTimeUpperBound, ComparisonOperator.LessOrEqual, InvariantDateTimeInTheRange)] + [InlineData(InvariantDateTimeLowerBound, InvariantDateTimeUpperBound, ComparisonOperator.LessOrEqual, InvariantDateTimeLowerBound)] + [InlineData(InvariantDateTimeUpperBound, InvariantDateTimeLowerBound, ComparisonOperator.GreaterThan, InvariantDateTimeInTheRange)] + [InlineData(InvariantDateTimeUpperBound, InvariantDateTimeLowerBound, ComparisonOperator.GreaterThan, InvariantDateTimeLowerBound)] + [InlineData(InvariantDateTimeUpperBound, InvariantDateTimeLowerBound, ComparisonOperator.GreaterOrEqual, InvariantDateTimeInTheRange)] + [InlineData(InvariantDateTimeUpperBound, InvariantDateTimeLowerBound, ComparisonOperator.GreaterOrEqual, InvariantDateTimeUpperBound)] + public async Task Can_filter_comparison_on_DateTimeOffset(string matchingValue, string nonMatchingValue, ComparisonOperator filterOperator, + string filterValue) + { + // Arrange + var resource = new FilterableResource + { + SomeDateTimeOffset = DateTime.Parse(matchingValue, CultureInfo.InvariantCulture).AsUtc() + }; + + var otherResource = new FilterableResource + { + SomeDateTimeOffset = DateTime.Parse(nonMatchingValue, CultureInfo.InvariantCulture).AsUtc() + }; + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + await dbContext.ClearTableAsync(); + dbContext.FilterableResources.AddRange(resource, otherResource); + await dbContext.SaveChangesAsync(); + }); + + string route = $"/filterableResources?filter={filterOperator.ToString().Camelize()}(someDateTimeOffset,'{filterValue}Z')"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); + + responseDocument.Data.ManyValue.ShouldHaveCount(1); + + responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey("someDateTimeOffset").With(value => value.Should().Be(resource.SomeDateTimeOffset)); + } + + [Theory] + [InlineData(TimeSpanLowerBound, TimeSpanUpperBound, ComparisonOperator.LessThan, TimeSpanInTheRange)] + [InlineData(TimeSpanLowerBound, TimeSpanUpperBound, ComparisonOperator.LessThan, TimeSpanUpperBound)] + [InlineData(TimeSpanLowerBound, TimeSpanUpperBound, ComparisonOperator.LessOrEqual, TimeSpanInTheRange)] + [InlineData(TimeSpanLowerBound, TimeSpanUpperBound, ComparisonOperator.LessOrEqual, TimeSpanLowerBound)] + [InlineData(TimeSpanUpperBound, TimeSpanLowerBound, ComparisonOperator.GreaterThan, TimeSpanInTheRange)] + [InlineData(TimeSpanUpperBound, TimeSpanLowerBound, ComparisonOperator.GreaterThan, TimeSpanLowerBound)] + [InlineData(TimeSpanUpperBound, TimeSpanLowerBound, ComparisonOperator.GreaterOrEqual, TimeSpanInTheRange)] + [InlineData(TimeSpanUpperBound, TimeSpanLowerBound, ComparisonOperator.GreaterOrEqual, TimeSpanUpperBound)] + public async Task Can_filter_comparison_on_TimeSpan(string matchingValue, string nonMatchingValue, ComparisonOperator filterOperator, string filterValue) + { + // Arrange + var resource = new FilterableResource + { + SomeTimeSpan = TimeSpan.Parse(matchingValue, CultureInfo.InvariantCulture) + }; + + var otherResource = new FilterableResource + { + SomeTimeSpan = TimeSpan.Parse(nonMatchingValue, CultureInfo.InvariantCulture) + }; + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + await dbContext.ClearTableAsync(); + dbContext.FilterableResources.AddRange(resource, otherResource); + await dbContext.SaveChangesAsync(); + }); + + string route = $"/filterableResources?filter={filterOperator.ToString().Camelize()}(someTimeSpan,'{filterValue}')"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); + + responseDocument.Data.ManyValue.ShouldHaveCount(1); + responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey("someTimeSpan").With(value => value.Should().Be(resource.SomeTimeSpan)); + } + [Theory] [InlineData("The fox jumped over the lazy dog", "Other", TextMatchKind.Contains, "jumped")] [InlineData("The fox jumped over the lazy dog", "the fox...", TextMatchKind.Contains, "The")] @@ -236,6 +357,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => } [Theory] + [InlineData("yes", "no", "'yes'")] [InlineData("two", "one two", "'one','two','three'")] [InlineData("two", "nine", "'one','two','three','four','five'")] public async Task Can_filter_in_set(string matchingText, string nonMatchingText, string filterText) diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Filtering/FilterableResource.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Filtering/FilterableResource.cs index e474854..17ec845 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Filtering/FilterableResource.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Filtering/FilterableResource.cs @@ -57,6 +57,10 @@ public sealed class FilterableResource : HexStringMongoIdentifiable [Attr] public Guid? SomeNullableGuid { get; set; } + [Attr] + [BsonDateTimeOptions(Kind = DateTimeKind.Local)] + public DateTime SomeDateTimeInLocalZone { get; set; } + [Attr] public DateTime SomeDateTimeInUtcZone { get; set; } From 1607bfd7bb81564ec6a64ed4622c4d940b0ac270 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Tue, 7 Feb 2023 17:53:16 +0100 Subject: [PATCH 2/2] Increment version to 5.1.2 --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index a11f520..a9d5421 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,7 +4,7 @@ 6.0.* 5.1.2 2.15.0 - 5.1.0 + 5.1.2 $(MSBuildThisFileDirectory)CodingGuidelines.ruleset 9999 enable