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 pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy