From 39d97500e98468c83a7b8e91b19f70ae7d9b0514 Mon Sep 17 00:00:00 2001 From: Maksym Sadovnychyy Date: Mon, 30 Dec 2024 21:24:16 +0100 Subject: [PATCH] (bugfix): paged request filter should be predicate --- .../Webapi/Models/PagedRequestTests.cs | 90 ++++++++----------- src/MaksIT.Core/MaksIT.Core.csproj | 2 +- src/MaksIT.Core/Webapi/Models/PagedRequest.cs | 26 +++--- 3 files changed, 55 insertions(+), 63 deletions(-) diff --git a/src/MaksIT.Core.Tests/Webapi/Models/PagedRequestTests.cs b/src/MaksIT.Core.Tests/Webapi/Models/PagedRequestTests.cs index 49c344d..42ef44e 100644 --- a/src/MaksIT.Core.Tests/Webapi/Models/PagedRequestTests.cs +++ b/src/MaksIT.Core.Tests/Webapi/Models/PagedRequestTests.cs @@ -1,10 +1,12 @@ -namespace MaksIT.Core.Tests.Webapi.Models; +using System.Linq; +using Xunit; +using MaksIT.Core.Webapi.Models; // Ensure namespace matches the actual namespace of PagedRequest public class PagedRequestTests { public class TestEntity { - public string? Name { get; set; } - public int Age { get; set; } + public required string Name { get; set; } + public required int Age { get; set; } } // Setup a mock IQueryable to test against @@ -17,7 +19,7 @@ public class PagedRequestTests { } [Fact] - public void ApplyFilters_ShouldHandleEqualsOperator() { + public void BuildFilterExpression_ShouldHandleEqualsOperator() { // Arrange var queryable = GetTestQueryable(); var request = new PagedRequest { @@ -25,147 +27,131 @@ public class PagedRequestTests { }; // Act - var filtered = request.ApplyFilters(queryable); + var predicate = request.BuildFilterExpression(); + var filtered = queryable.Where(predicate).ToList(); // Assert Assert.Contains(filtered, t => t.Name == "John"); Assert.Single(filtered); } + // Add similar changes for other tests [Fact] - public void ApplyFilters_ShouldHandleNotEqualsOperator() { - // Arrange + public void BuildFilterExpression_ShouldHandleNotEqualsOperator() { var queryable = GetTestQueryable(); var request = new PagedRequest { Filters = "Name != \"John\"" }; - // Act - var filtered = request.ApplyFilters(queryable); + var predicate = request.BuildFilterExpression(); + var filtered = queryable.Where(predicate).ToList(); - // Assert Assert.DoesNotContain(filtered, t => t.Name == "John"); } [Fact] - public void ApplyFilters_ShouldHandleGreaterThanOperator() { - // Arrange + public void BuildFilterExpression_ShouldHandleGreaterThanOperator() { var queryable = GetTestQueryable(); var request = new PagedRequest { Filters = "Age > 30" }; - // Act - var filtered = request.ApplyFilters(queryable); + var predicate = request.BuildFilterExpression(); + var filtered = queryable.Where(predicate).ToList(); - // Assert Assert.All(filtered, t => Assert.True(t.Age > 30)); } [Fact] - public void ApplyFilters_ShouldHandleLessThanOperator() { - // Arrange + public void BuildFilterExpression_ShouldHandleLessThanOperator() { var queryable = GetTestQueryable(); var request = new PagedRequest { Filters = "Age < 30" }; - // Act - var filtered = request.ApplyFilters(queryable); + var predicate = request.BuildFilterExpression(); + var filtered = queryable.Where(predicate).ToList(); - // Assert Assert.All(filtered, t => Assert.True(t.Age < 30)); } [Fact] - public void ApplyFilters_ShouldHandleAndOperator() { - // Arrange + public void BuildFilterExpression_ShouldHandleAndOperator() { var queryable = GetTestQueryable(); var request = new PagedRequest { Filters = "Name == \"John\" && Age > 30" }; - // Act - var filtered = request.ApplyFilters(queryable); + var predicate = request.BuildFilterExpression(); + var filtered = queryable.Where(predicate).ToList(); - // Assert Assert.Contains(filtered, t => t.Name == "John" && t.Age > 30); Assert.Single(filtered); } [Fact] - public void ApplyFilters_ShouldHandleOrOperator() { - // Arrange + public void BuildFilterExpression_ShouldHandleOrOperator() { var queryable = GetTestQueryable(); var request = new PagedRequest { Filters = "Name == \"John\" || Age > 30" }; - // Act - var filtered = request.ApplyFilters(queryable); + var predicate = request.BuildFilterExpression(); + var filtered = queryable.Where(predicate).ToList(); - // Assert Assert.Contains(filtered, t => t.Name == "John" || t.Age > 30); } [Fact] - public void ApplyFilters_ShouldHandleNegation() { - // Arrange + public void BuildFilterExpression_ShouldHandleNegation() { var queryable = GetTestQueryable(); var request = new PagedRequest { Filters = "!(Name == \"John\")" }; - // Act - var filtered = request.ApplyFilters(queryable); + var predicate = request.BuildFilterExpression(); + var filtered = queryable.Where(predicate).ToList(); - // Assert Assert.DoesNotContain(filtered, t => t.Name == "John"); } [Fact] - public void ApplyFilters_ShouldHandleContainsOperator() { - // Arrange + public void BuildFilterExpression_ShouldHandleContainsOperator() { var queryable = GetTestQueryable(); var request = new PagedRequest { Filters = "Name.Contains(\"oh\")" }; - // Act - var filtered = request.ApplyFilters(queryable); + var predicate = request.BuildFilterExpression(); + var filtered = queryable.Where(predicate).ToList(); - // Assert Assert.Contains(filtered, t => t.Name.Contains("oh")); } [Fact] - public void ApplyFilters_ShouldHandleStartsWithOperator() { - // Arrange + public void BuildFilterExpression_ShouldHandleStartsWithOperator() { var queryable = GetTestQueryable(); var request = new PagedRequest { Filters = "Name.StartsWith(\"Jo\")" }; - // Act - var filtered = request.ApplyFilters(queryable); + var predicate = request.BuildFilterExpression(); + var filtered = queryable.Where(predicate).ToList(); - // Assert - Assert.Contains(filtered, t => t.Name.StartsWith("John")); - Assert.Single(filtered); // Assuming only "Johnny" starts with "John" + Assert.Contains(filtered, t => t.Name.StartsWith("Jo")); + Assert.Single(filtered); // Assuming only "John" starts with "Jo" } [Fact] - public void ApplyFilters_ShouldHandleEndsWithOperator() { - // Arrange + public void BuildFilterExpression_ShouldHandleEndsWithOperator() { var queryable = GetTestQueryable(); var request = new PagedRequest { Filters = "Name.EndsWith(\"hn\")" }; - // Act - var filtered = request.ApplyFilters(queryable); + var predicate = request.BuildFilterExpression(); + var filtered = queryable.Where(predicate).ToList(); - // Assert Assert.Contains(filtered, t => t.Name.EndsWith("hn")); } } diff --git a/src/MaksIT.Core/MaksIT.Core.csproj b/src/MaksIT.Core/MaksIT.Core.csproj index 8fe9f44..952b136 100644 --- a/src/MaksIT.Core/MaksIT.Core.csproj +++ b/src/MaksIT.Core/MaksIT.Core.csproj @@ -8,7 +8,7 @@ MaksIT.Core - 1.2.9 + 1.3.0 Maksym Sadovnychyy MAKS-IT MaksIT.Core diff --git a/src/MaksIT.Core/Webapi/Models/PagedRequest.cs b/src/MaksIT.Core/Webapi/Models/PagedRequest.cs index 177a7ce..1c3f500 100644 --- a/src/MaksIT.Core/Webapi/Models/PagedRequest.cs +++ b/src/MaksIT.Core/Webapi/Models/PagedRequest.cs @@ -1,5 +1,5 @@ using System.Linq.Dynamic.Core; - +using System.Linq.Expressions; using MaksIT.Core.Abstractions.Webapi; public class PagedRequest : RequestModelBase { @@ -10,16 +10,22 @@ public class PagedRequest : RequestModelBase { public string? SortBy { get; set; } public bool IsAscending { get; set; } = true; - public IQueryable ApplyFilters(IQueryable query) { - if (!string.IsNullOrWhiteSpace(Filters)) { - query = query.Where(Filters); // Filters interpreted directly - } + public Expression> BuildFilterExpression() { + if (string.IsNullOrWhiteSpace(Filters)) + return x => true; // Returns an expression that doesn't filter anything. - if (!string.IsNullOrWhiteSpace(SortBy)) { - var direction = IsAscending ? "ascending" : "descending"; - query = query.OrderBy($"{SortBy} {direction}"); - } + // Parse the filter string into a dynamic lambda expression. + var predicate = DynamicExpressionParser.ParseLambda( + new ParsingConfig(), false, Filters); + return predicate; + } - return query.Skip((PageNumber - 1) * PageSize).Take(PageSize); + public Func, IOrderedQueryable> BuildSortExpression() { + if (string.IsNullOrWhiteSpace(SortBy)) + return q => (IOrderedQueryable)q; // Cast to IOrderedQueryable + + var direction = IsAscending ? "ascending" : "descending"; + // Return a Func that takes an IQueryable and applies the sorting to it. + return q => (IOrderedQueryable)q.OrderBy($"{SortBy} {direction}"); } }