(bugfix): paged request filter should be predicate

This commit is contained in:
Maksym Sadovnychyy 2024-12-30 21:24:16 +01:00
parent 896bcba334
commit 39d97500e9
3 changed files with 55 additions and 63 deletions

View File

@ -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 PagedRequestTests {
public class TestEntity { public class TestEntity {
public string? Name { get; set; } public required string Name { get; set; }
public int Age { get; set; } public required int Age { get; set; }
} }
// Setup a mock IQueryable to test against // Setup a mock IQueryable to test against
@ -17,7 +19,7 @@ public class PagedRequestTests {
} }
[Fact] [Fact]
public void ApplyFilters_ShouldHandleEqualsOperator() { public void BuildFilterExpression_ShouldHandleEqualsOperator() {
// Arrange // Arrange
var queryable = GetTestQueryable(); var queryable = GetTestQueryable();
var request = new PagedRequest { var request = new PagedRequest {
@ -25,147 +27,131 @@ public class PagedRequestTests {
}; };
// Act // Act
var filtered = request.ApplyFilters(queryable); var predicate = request.BuildFilterExpression<TestEntity>();
var filtered = queryable.Where(predicate).ToList();
// Assert // Assert
Assert.Contains(filtered, t => t.Name == "John"); Assert.Contains(filtered, t => t.Name == "John");
Assert.Single(filtered); Assert.Single(filtered);
} }
// Add similar changes for other tests
[Fact] [Fact]
public void ApplyFilters_ShouldHandleNotEqualsOperator() { public void BuildFilterExpression_ShouldHandleNotEqualsOperator() {
// Arrange
var queryable = GetTestQueryable(); var queryable = GetTestQueryable();
var request = new PagedRequest { var request = new PagedRequest {
Filters = "Name != \"John\"" Filters = "Name != \"John\""
}; };
// Act var predicate = request.BuildFilterExpression<TestEntity>();
var filtered = request.ApplyFilters(queryable); var filtered = queryable.Where(predicate).ToList();
// Assert
Assert.DoesNotContain(filtered, t => t.Name == "John"); Assert.DoesNotContain(filtered, t => t.Name == "John");
} }
[Fact] [Fact]
public void ApplyFilters_ShouldHandleGreaterThanOperator() { public void BuildFilterExpression_ShouldHandleGreaterThanOperator() {
// Arrange
var queryable = GetTestQueryable(); var queryable = GetTestQueryable();
var request = new PagedRequest { var request = new PagedRequest {
Filters = "Age > 30" Filters = "Age > 30"
}; };
// Act var predicate = request.BuildFilterExpression<TestEntity>();
var filtered = request.ApplyFilters(queryable); var filtered = queryable.Where(predicate).ToList();
// Assert
Assert.All(filtered, t => Assert.True(t.Age > 30)); Assert.All(filtered, t => Assert.True(t.Age > 30));
} }
[Fact] [Fact]
public void ApplyFilters_ShouldHandleLessThanOperator() { public void BuildFilterExpression_ShouldHandleLessThanOperator() {
// Arrange
var queryable = GetTestQueryable(); var queryable = GetTestQueryable();
var request = new PagedRequest { var request = new PagedRequest {
Filters = "Age < 30" Filters = "Age < 30"
}; };
// Act var predicate = request.BuildFilterExpression<TestEntity>();
var filtered = request.ApplyFilters(queryable); var filtered = queryable.Where(predicate).ToList();
// Assert
Assert.All(filtered, t => Assert.True(t.Age < 30)); Assert.All(filtered, t => Assert.True(t.Age < 30));
} }
[Fact] [Fact]
public void ApplyFilters_ShouldHandleAndOperator() { public void BuildFilterExpression_ShouldHandleAndOperator() {
// Arrange
var queryable = GetTestQueryable(); var queryable = GetTestQueryable();
var request = new PagedRequest { var request = new PagedRequest {
Filters = "Name == \"John\" && Age > 30" Filters = "Name == \"John\" && Age > 30"
}; };
// Act var predicate = request.BuildFilterExpression<TestEntity>();
var filtered = request.ApplyFilters(queryable); var filtered = queryable.Where(predicate).ToList();
// Assert
Assert.Contains(filtered, t => t.Name == "John" && t.Age > 30); Assert.Contains(filtered, t => t.Name == "John" && t.Age > 30);
Assert.Single(filtered); Assert.Single(filtered);
} }
[Fact] [Fact]
public void ApplyFilters_ShouldHandleOrOperator() { public void BuildFilterExpression_ShouldHandleOrOperator() {
// Arrange
var queryable = GetTestQueryable(); var queryable = GetTestQueryable();
var request = new PagedRequest { var request = new PagedRequest {
Filters = "Name == \"John\" || Age > 30" Filters = "Name == \"John\" || Age > 30"
}; };
// Act var predicate = request.BuildFilterExpression<TestEntity>();
var filtered = request.ApplyFilters(queryable); var filtered = queryable.Where(predicate).ToList();
// Assert
Assert.Contains(filtered, t => t.Name == "John" || t.Age > 30); Assert.Contains(filtered, t => t.Name == "John" || t.Age > 30);
} }
[Fact] [Fact]
public void ApplyFilters_ShouldHandleNegation() { public void BuildFilterExpression_ShouldHandleNegation() {
// Arrange
var queryable = GetTestQueryable(); var queryable = GetTestQueryable();
var request = new PagedRequest { var request = new PagedRequest {
Filters = "!(Name == \"John\")" Filters = "!(Name == \"John\")"
}; };
// Act var predicate = request.BuildFilterExpression<TestEntity>();
var filtered = request.ApplyFilters(queryable); var filtered = queryable.Where(predicate).ToList();
// Assert
Assert.DoesNotContain(filtered, t => t.Name == "John"); Assert.DoesNotContain(filtered, t => t.Name == "John");
} }
[Fact] [Fact]
public void ApplyFilters_ShouldHandleContainsOperator() { public void BuildFilterExpression_ShouldHandleContainsOperator() {
// Arrange
var queryable = GetTestQueryable(); var queryable = GetTestQueryable();
var request = new PagedRequest { var request = new PagedRequest {
Filters = "Name.Contains(\"oh\")" Filters = "Name.Contains(\"oh\")"
}; };
// Act var predicate = request.BuildFilterExpression<TestEntity>();
var filtered = request.ApplyFilters(queryable); var filtered = queryable.Where(predicate).ToList();
// Assert
Assert.Contains(filtered, t => t.Name.Contains("oh")); Assert.Contains(filtered, t => t.Name.Contains("oh"));
} }
[Fact] [Fact]
public void ApplyFilters_ShouldHandleStartsWithOperator() { public void BuildFilterExpression_ShouldHandleStartsWithOperator() {
// Arrange
var queryable = GetTestQueryable(); var queryable = GetTestQueryable();
var request = new PagedRequest { var request = new PagedRequest {
Filters = "Name.StartsWith(\"Jo\")" Filters = "Name.StartsWith(\"Jo\")"
}; };
// Act var predicate = request.BuildFilterExpression<TestEntity>();
var filtered = request.ApplyFilters(queryable); var filtered = queryable.Where(predicate).ToList();
// Assert Assert.Contains(filtered, t => t.Name.StartsWith("Jo"));
Assert.Contains(filtered, t => t.Name.StartsWith("John")); Assert.Single(filtered); // Assuming only "John" starts with "Jo"
Assert.Single(filtered); // Assuming only "Johnny" starts with "John"
} }
[Fact] [Fact]
public void ApplyFilters_ShouldHandleEndsWithOperator() { public void BuildFilterExpression_ShouldHandleEndsWithOperator() {
// Arrange
var queryable = GetTestQueryable(); var queryable = GetTestQueryable();
var request = new PagedRequest { var request = new PagedRequest {
Filters = "Name.EndsWith(\"hn\")" Filters = "Name.EndsWith(\"hn\")"
}; };
// Act var predicate = request.BuildFilterExpression<TestEntity>();
var filtered = request.ApplyFilters(queryable); var filtered = queryable.Where(predicate).ToList();
// Assert
Assert.Contains(filtered, t => t.Name.EndsWith("hn")); Assert.Contains(filtered, t => t.Name.EndsWith("hn"));
} }
} }

View File

@ -8,7 +8,7 @@
<!-- NuGet package metadata --> <!-- NuGet package metadata -->
<PackageId>MaksIT.Core</PackageId> <PackageId>MaksIT.Core</PackageId>
<Version>1.2.9</Version> <Version>1.3.0</Version>
<Authors>Maksym Sadovnychyy</Authors> <Authors>Maksym Sadovnychyy</Authors>
<Company>MAKS-IT</Company> <Company>MAKS-IT</Company>
<Product>MaksIT.Core</Product> <Product>MaksIT.Core</Product>

View File

@ -1,5 +1,5 @@
using System.Linq.Dynamic.Core; using System.Linq.Dynamic.Core;
using System.Linq.Expressions;
using MaksIT.Core.Abstractions.Webapi; using MaksIT.Core.Abstractions.Webapi;
public class PagedRequest : RequestModelBase { public class PagedRequest : RequestModelBase {
@ -10,16 +10,22 @@ public class PagedRequest : RequestModelBase {
public string? SortBy { get; set; } public string? SortBy { get; set; }
public bool IsAscending { get; set; } = true; public bool IsAscending { get; set; } = true;
public IQueryable<T> ApplyFilters<T>(IQueryable<T> query) { public Expression<Func<T, bool>> BuildFilterExpression<T>() {
if (!string.IsNullOrWhiteSpace(Filters)) { if (string.IsNullOrWhiteSpace(Filters))
query = query.Where(Filters); // Filters interpreted directly return x => true; // Returns an expression that doesn't filter anything.
}
if (!string.IsNullOrWhiteSpace(SortBy)) { // Parse the filter string into a dynamic lambda expression.
var direction = IsAscending ? "ascending" : "descending"; var predicate = DynamicExpressionParser.ParseLambda<T, bool>(
query = query.OrderBy($"{SortBy} {direction}"); new ParsingConfig(), false, Filters);
} return predicate;
}
return query.Skip((PageNumber - 1) * PageSize).Take(PageSize); public Func<IQueryable<T>, IOrderedQueryable<T>> BuildSortExpression<T>() {
if (string.IsNullOrWhiteSpace(SortBy))
return q => (IOrderedQueryable<T>)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<T>)q.OrderBy($"{SortBy} {direction}");
} }
} }