(bugfix): paged request missing !=, ! and tests

This commit is contained in:
Maksym Sadovnychyy 2024-10-18 20:28:52 +02:00
parent 346b891a5f
commit f463862b7c
6 changed files with 355 additions and 127 deletions

View File

@ -6,34 +6,6 @@ using MaksIT.Core.Extensions;
namespace MaksIT.Core.Tests.Extensions;
public class ExpressionExtensionsTests {
[Fact]
public void CreateContainsPredicate_ShouldReturnTrue_WhenIdIsInList() {
// Arrange
var ids = new List<Guid> { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() };
var targetId = ids[1];
var predicate = ExpressionExtensions.CreateContainsPredicate<TestEntity>(ids, nameof(TestEntity.Id));
// Act
var result = predicate.Compile()(new TestEntity { Id = targetId });
// Assert
Assert.True(result);
}
[Fact]
public void CreateContainsPredicate_ShouldReturnFalse_WhenIdIsNotInList() {
// Arrange
var ids = new List<Guid> { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() };
var targetId = Guid.NewGuid();
var predicate = ExpressionExtensions.CreateContainsPredicate<TestEntity>(ids, nameof(TestEntity.Id));
// Act
var result = predicate.Compile()(new TestEntity { Id = targetId });
// Assert
Assert.False(result);
}
[Fact]
public void CombineWith_ShouldCombineTwoPredicates() {
// Arrange

View File

@ -0,0 +1,136 @@

using MaksIT.Core.Webapi.Models;
namespace MaksIT.Core.Tests.Webapi.Models;
public class PagedRequestTests {
[Fact]
public void BuildFilterExpression_ShouldReturnNull_WhenFilterIsEmpty() {
// Arrange
var request = new PagedRequest();
// Act
var result = request.BuildFilterExpression<TestEntity>(null);
// Assert
Assert.Null(result);
}
[Fact]
public void BuildFilterExpression_ShouldHandleEqualsOperator() {
// Arrange
var request = new PagedRequest {
Filters = "Name='John'"
};
// Act
var expression = request.BuildFilterExpression<TestEntity>(request.Filters);
var compiled = expression!.Compile();
// Assert
var testEntity = new TestEntity { Name = "John" };
Assert.True(compiled(testEntity));
}
[Fact]
public void BuildFilterExpression_ShouldHandleNotEqualsOperator() {
// Arrange
var request = new PagedRequest {
Filters = "Name!='John'"
};
// Act
var expression = request.BuildFilterExpression<TestEntity>(request.Filters);
var compiled = expression!.Compile();
// Assert
var testEntity = new TestEntity { Name = "John" };
Assert.False(compiled(testEntity));
}
[Fact]
public void BuildFilterExpression_ShouldHandleGreaterThanOperator() {
// Arrange
var request = new PagedRequest {
Filters = "Age>30"
};
// Act
var expression = request.BuildFilterExpression<TestEntity>(request.Filters);
var compiled = expression!.Compile();
// Assert
var testEntity = new TestEntity { Age = 31 };
Assert.True(compiled(testEntity));
}
[Fact]
public void BuildFilterExpression_ShouldHandleLessThanOperator() {
// Arrange
var request = new PagedRequest {
Filters = "Age<30"
};
// Act
var expression = request.BuildFilterExpression<TestEntity>(request.Filters);
var compiled = expression!.Compile();
// Assert
var testEntity = new TestEntity { Age = 29 };
Assert.True(compiled(testEntity));
}
[Fact]
public void BuildFilterExpression_ShouldHandleAndOperator() {
// Arrange
var request = new PagedRequest {
Filters = "Name='John' && Age>30"
};
// Act
var expression = request.BuildFilterExpression<TestEntity>(request.Filters);
var compiled = expression!.Compile();
// Assert
var testEntity = new TestEntity { Name = "John", Age = 31 };
Assert.True(compiled(testEntity));
}
[Fact]
public void BuildFilterExpression_ShouldHandleOrOperator() {
// Arrange
var request = new PagedRequest {
Filters = "Name='John' || Age>30"
};
// Act
var expression = request.BuildFilterExpression<TestEntity>(request.Filters);
var compiled = expression!.Compile();
// Assert
var testEntity = new TestEntity { Name = "Doe", Age = 31 };
Assert.True(compiled(testEntity));
}
[Fact]
public void BuildFilterExpression_ShouldHandleNegation() {
// Arrange
var request = new PagedRequest {
Filters = "!Name='John'"
};
// Act
var expression = request.BuildFilterExpression<TestEntity>(request.Filters);
var compiled = expression!.Compile();
// Assert
var testEntity = new TestEntity { Name = "Doe" };
Assert.True(compiled(testEntity));
}
}
// Helper class for testing purposes
public class TestEntity {
public string Name { get; set; }
public int Age { get; set; }
}

View File

@ -0,0 +1,86 @@
using MaksIT.Core.Webapi.Models;
namespace MaksIT.Core.Tests.Webapi.Models;
public class PagedResponseTests {
[Fact]
public void TotalPages_ShouldCalculateCorrectly() {
// Arrange
var items = new List<string> { "Item1", "Item2" };
var response = new PagedResponse<string>(items, 10, 1, 2);
// Act
var totalPages = response.TotalPages;
// Assert
Assert.Equal(5, totalPages);
}
[Fact]
public void HasPreviousPage_ShouldReturnTrue_WhenPageNumberGreaterThan1() {
// Arrange
var items = new List<string> { "Item1", "Item2" };
var response = new PagedResponse<string>(items, 10, 2, 2);
// Act
var hasPreviousPage = response.HasPreviousPage;
// Assert
Assert.True(hasPreviousPage);
}
[Fact]
public void HasPreviousPage_ShouldReturnFalse_WhenPageNumberIs1() {
// Arrange
var items = new List<string> { "Item1", "Item2" };
var response = new PagedResponse<string>(items, 10, 1, 2);
// Act
var hasPreviousPage = response.HasPreviousPage;
// Assert
Assert.False(hasPreviousPage);
}
[Fact]
public void HasNextPage_ShouldReturnTrue_WhenPageNumberLessThanTotalPages() {
// Arrange
var items = new List<string> { "Item1", "Item2" };
var response = new PagedResponse<string>(items, 10, 1, 2);
// Act
var hasNextPage = response.HasNextPage;
// Assert
Assert.True(hasNextPage);
}
[Fact]
public void HasNextPage_ShouldReturnFalse_WhenPageNumberEqualsTotalPages() {
// Arrange
var items = new List<string> { "Item1", "Item2" };
var response = new PagedResponse<string>(items, 10, 5, 2);
// Act
var hasNextPage = response.HasNextPage;
// Assert
Assert.False(hasNextPage);
}
[Fact]
public void Constructor_ShouldInitializePropertiesCorrectly() {
// Arrange
var items = new List<string> { "Item1", "Item2" };
// Act
var response = new PagedResponse<string>(items, 10, 1, 2);
// Assert
Assert.Equal(items, response.Items);
Assert.Equal(10, response.TotalCount);
Assert.Equal(1, response.PageNumber);
Assert.Equal(2, response.PageSize);
}
}

View File

@ -8,15 +8,6 @@ namespace MaksIT.Core.Extensions;
public static class ExpressionExtensions {
public static Expression<Func<T, bool>> CreateContainsPredicate<T>(IEnumerable<Guid> ids, string propertyName) {
var parameter = Expression.Parameter(typeof(T), "x");
var property = Expression.Property(parameter, propertyName);
var containsMethod = typeof(List<Guid>).GetMethod("Contains", new[] { typeof(Guid) });
var containsCall = Expression.Call(Expression.Constant(ids), containsMethod!, property);
return Expression.Lambda<Func<T, bool>>(containsCall, parameter);
}
public static Expression<Func<T, bool>> CombineWith<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second) {
var parameter = first.Parameters[0];
var visitor = new SubstituteParameterVisitor(second.Parameters[0], parameter);

View File

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

View File

@ -1,12 +1,9 @@
using System.Linq.Expressions;
using MaksIT.Core.Extensions;
using MaksIT.Core.Abstractions.Webapi;
namespace MaksIT.Core.Webapi.Models;
public class PagedRequest : RequestModelBase {
namespace MaksIT.Core.Webapi.Models {
public class PagedRequest : RequestModelBase {
public int PageSize { get; set; } = 100;
public int PageNumber { get; set; } = 1;
@ -45,45 +42,90 @@ public class PagedRequest : RequestModelBase {
}
var parameter = Expression.Parameter(typeof(T), "x");
var expressions = new List<Expression>();
Expression? combinedExpression = null;
var filters = filter.Split(new[] { "AND", "OR" }, StringSplitOptions.None);
// Split filters based on && and || operators
var tokens = filter.Split(new[] { "&&", "||" }, StringSplitOptions.None);
var operators = new List<string>();
// Extract operators (&&, ||) from the original filter string
int lastIndex = 0;
for (int i = 0; i < filter.Length; i++) {
if (filter.Substring(i).StartsWith("&&")) {
operators.Add("&&");
lastIndex = i + 2;
}
else if (filter.Substring(i).StartsWith("||")) {
operators.Add("||");
lastIndex = i + 2;
}
}
for (int i = 0; i < tokens.Length; i++) {
string processedFilter = tokens[i].Trim();
bool isNegated = false;
// Check for '!' at the beginning for negation
if (processedFilter.StartsWith("!")) {
isNegated = true;
processedFilter = processedFilter.Substring(1).Trim(); // Remove '!' and trim
}
foreach (var subFilter in filters) {
Expression? expression = null;
if (subFilter.Contains('=')) {
var parts = subFilter.Split('=');
var propertyName = parts[0].Trim();
var value = parts[1].Trim().Replace("'", "");
var property = Expression.Property(parameter, propertyName);
var constant = Expression.Constant(ConvertValue(property.Type, value));
expression = Expression.Equal(property, constant);
if (processedFilter.Contains("!=")) {
var parts = processedFilter.Split(new[] { "!=" }, StringSplitOptions.None);
expression = BuildEqualityExpression(parameter, parts[0], parts[1], isNegated: true);
}
else if (subFilter.Contains('>') || subFilter.Contains('<')) {
expression = BuildComparisonExpression(subFilter, parameter);
else if (processedFilter.Contains('=')) {
var parts = processedFilter.Split('=');
expression = BuildEqualityExpression(parameter, parts[0], parts[1], isNegated: false);
}
else if (processedFilter.Contains('>') || processedFilter.Contains('<')) {
// Handle comparison (>, <, >=, <=)
expression = BuildComparisonExpression(processedFilter, parameter);
}
if (expression != null) {
expressions.Add(expression);
// Apply negation if '!' was found at the beginning
if (isNegated && expression != null) {
expression = Expression.Not(expression);
}
// Combine the current expression with the previous one using the correct operator
if (combinedExpression == null) {
combinedExpression = expression;
}
else if (i - 1 < operators.Count) // Ensure we don't exceed the operators list size
{
var operatorType = operators[i - 1];
combinedExpression = operatorType == "&&"
? Expression.AndAlso(combinedExpression, expression)
: Expression.OrElse(combinedExpression, expression);
}
}
if (!expressions.Any()) {
return null;
return combinedExpression != null
? Expression.Lambda<Func<T, bool>>(combinedExpression, parameter)
: null;
}
var combinedExpression = expressions.Aggregate(Expression.AndAlso);
private static Expression BuildEqualityExpression(ParameterExpression parameter, string propertyName, string value, bool isNegated) {
var property = Expression.Property(parameter, propertyName.Trim());
var constant = Expression.Constant(ConvertValue(property.Type, value.Trim().Replace("'", "")));
return Expression.Lambda<Func<T, bool>>(combinedExpression, parameter);
return isNegated
? Expression.NotEqual(property, constant)
: Expression.Equal(property, constant);
}
private static Expression? BuildComparisonExpression(string subFilter, ParameterExpression parameter) {
var comparisonType = subFilter.Contains(">=") ? ">=" :
subFilter.Contains("<=") ? "<=" :
subFilter.Contains(">") ? ">" : "<";
var comparisonType = subFilter.Contains(">=")
? ">="
: subFilter.Contains("<=")
? "<="
: subFilter.Contains(">")
? ">"
: "<";
var parts = subFilter.Split(new[] { '>', '<', '=', ' ' }, StringSplitOptions.RemoveEmptyEntries);
var propertyName = parts[0].Trim();
@ -109,4 +151,5 @@ public class PagedRequest : RequestModelBase {
_ => value
};
}
}
}