mirror of
https://github.com/Biarity/Sieve.git
synced 2024-11-24 22:42:30 +01:00
OrderByDynamic is modified to be able to handle inherited members, such as interface members.
SieveProcessor is modified to pass propertyInfo to OrderByDynamic to avoid reattainment of propertyInfo required in Expression.MakeMemberAccess. SieveProcessor is modified to be able to handle possible multiple incompatible customMethods via AggregateException. Corresponding interfaces are generated for entities with related inheritance. ApplicationSieveProcessor is modified to include interface members. SieveCustomFilterMethods and SieveCustomSortMethod are modified to include interface related custom method modifications. Interface accessed unit tests are added.
This commit is contained in:
parent
51b5356ec7
commit
a4509bb8f0
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
namespace Sieve.Extensions
|
namespace Sieve.Extensions
|
||||||
{
|
{
|
||||||
@ -9,10 +10,11 @@ namespace Sieve.Extensions
|
|||||||
public static IQueryable<TEntity> OrderByDynamic<TEntity>(
|
public static IQueryable<TEntity> OrderByDynamic<TEntity>(
|
||||||
this IQueryable<TEntity> source,
|
this IQueryable<TEntity> source,
|
||||||
string fullPropertyName,
|
string fullPropertyName,
|
||||||
|
PropertyInfo propertyInfo,
|
||||||
bool desc,
|
bool desc,
|
||||||
bool useThenBy)
|
bool useThenBy)
|
||||||
{
|
{
|
||||||
var lambda = GenerateLambdaWithSafeMemberAccess<TEntity>(fullPropertyName);
|
var lambda = GenerateLambdaWithSafeMemberAccess<TEntity>(fullPropertyName, propertyInfo);
|
||||||
|
|
||||||
var command = desc
|
var command = desc
|
||||||
? (useThenBy ? "ThenByDescending" : "OrderByDescending")
|
? (useThenBy ? "ThenByDescending" : "OrderByDescending")
|
||||||
@ -28,15 +30,27 @@ namespace Sieve.Extensions
|
|||||||
return source.Provider.CreateQuery<TEntity>(resultExpression);
|
return source.Provider.CreateQuery<TEntity>(resultExpression);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Expression<Func<TEntity, object>> GenerateLambdaWithSafeMemberAccess<TEntity>(string fullPropertyName)
|
private static Expression<Func<TEntity, object>> GenerateLambdaWithSafeMemberAccess<TEntity>
|
||||||
|
(
|
||||||
|
string fullPropertyName,
|
||||||
|
PropertyInfo propertyInfo
|
||||||
|
)
|
||||||
{
|
{
|
||||||
var parameter = Expression.Parameter(typeof(TEntity), "e");
|
var parameter = Expression.Parameter(typeof(TEntity), "e");
|
||||||
Expression propertyValue = parameter;
|
Expression propertyValue = parameter;
|
||||||
Expression nullCheck = null;
|
Expression nullCheck = null;
|
||||||
|
|
||||||
foreach (var name in fullPropertyName.Split('.'))
|
foreach (var name in fullPropertyName.Split('.'))
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
propertyValue = Expression.PropertyOrField(propertyValue, name);
|
propertyValue = Expression.PropertyOrField(propertyValue, name);
|
||||||
|
}
|
||||||
|
catch (ArgumentException)
|
||||||
|
{
|
||||||
|
// name is not a direct property of field of propertyValue expression. construct a memberAccess then.
|
||||||
|
propertyValue = Expression.MakeMemberAccess(propertyValue, propertyInfo);
|
||||||
|
}
|
||||||
|
|
||||||
if (propertyValue.Type.IsNullable())
|
if (propertyValue.Type.IsNullable())
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
@ -340,7 +341,7 @@ namespace Sieve.Services
|
|||||||
|
|
||||||
if (property != null)
|
if (property != null)
|
||||||
{
|
{
|
||||||
result = result.OrderByDynamic(fullName, sortTerm.Descending, useThenBy);
|
result = result.OrderByDynamic(fullName, property, sortTerm.Descending, useThenBy);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -461,16 +462,35 @@ namespace Sieve.Services
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var incompatibleCustomMethod = parent?.GetType()
|
var incompatibleCustomMethods = parent?
|
||||||
.GetMethod(name,
|
.GetType()
|
||||||
_options.Value.CaseSensitive ? BindingFlags.Default : BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
|
.GetMethods
|
||||||
|
(
|
||||||
|
_options.Value.CaseSensitive
|
||||||
|
? BindingFlags.Default
|
||||||
|
: BindingFlags.IgnoreCase | BindingFlags.Public |
|
||||||
|
BindingFlags.Instance
|
||||||
|
)
|
||||||
|
.Where(method => string.Equals(method.Name, name,
|
||||||
|
_options.Value.CaseSensitive
|
||||||
|
? StringComparison.InvariantCulture
|
||||||
|
: StringComparison.InvariantCultureIgnoreCase))
|
||||||
|
.ToList()
|
||||||
|
??
|
||||||
|
new List<MethodInfo>();
|
||||||
|
|
||||||
if (incompatibleCustomMethod != null)
|
if (incompatibleCustomMethods.Any())
|
||||||
{
|
{
|
||||||
var expected = typeof(IQueryable<TEntity>);
|
var incompatibles =
|
||||||
var actual = incompatibleCustomMethod.ReturnType;
|
from incompatibleCustomMethod in incompatibleCustomMethods
|
||||||
throw new SieveIncompatibleMethodException(name, expected, actual,
|
let expected = typeof(IQueryable<TEntity>)
|
||||||
|
let actual = incompatibleCustomMethod.ReturnType
|
||||||
|
select new SieveIncompatibleMethodException(name, expected, actual,
|
||||||
$"{name} failed. Expected a custom method for type {expected} but only found for type {actual}");
|
$"{name} failed. Expected a custom method for type {expected} but only found for type {actual}");
|
||||||
|
|
||||||
|
var aggregate = new AggregateException(incompatibles);
|
||||||
|
|
||||||
|
throw new SieveIncompatibleMethodException(aggregate.Message, aggregate);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
10
SieveUnitTests/Abstractions/Entity/IBaseEntity.cs
Normal file
10
SieveUnitTests/Abstractions/Entity/IBaseEntity.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace SieveUnitTests.Abstractions.Entity
|
||||||
|
{
|
||||||
|
public interface IBaseEntity
|
||||||
|
{
|
||||||
|
int Id { get; set; }
|
||||||
|
DateTimeOffset DateCreated { get; set; }
|
||||||
|
}
|
||||||
|
}
|
7
SieveUnitTests/Abstractions/Entity/IComment.cs
Normal file
7
SieveUnitTests/Abstractions/Entity/IComment.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
namespace SieveUnitTests.Abstractions.Entity
|
||||||
|
{
|
||||||
|
public interface IComment: IBaseEntity
|
||||||
|
{
|
||||||
|
string Text { get; set; }
|
||||||
|
}
|
||||||
|
}
|
24
SieveUnitTests/Abstractions/Entity/IPost.cs
Normal file
24
SieveUnitTests/Abstractions/Entity/IPost.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
using Sieve.Attributes;
|
||||||
|
using SieveUnitTests.Entities;
|
||||||
|
|
||||||
|
namespace SieveUnitTests.Abstractions.Entity
|
||||||
|
{
|
||||||
|
public interface IPost: IBaseEntity
|
||||||
|
{
|
||||||
|
[Sieve(CanFilter = true, CanSort = true)]
|
||||||
|
string Title { get; set; }
|
||||||
|
[Sieve(CanFilter = true, CanSort = true)]
|
||||||
|
int LikeCount { get; set; }
|
||||||
|
[Sieve(CanFilter = true, CanSort = true)]
|
||||||
|
int CommentCount { get; set; }
|
||||||
|
[Sieve(CanFilter = true, CanSort = true)]
|
||||||
|
int? CategoryId { get; set; }
|
||||||
|
[Sieve(CanFilter = true, CanSort = true)]
|
||||||
|
bool IsDraft { get; set; }
|
||||||
|
string ThisHasNoAttribute { get; set; }
|
||||||
|
string ThisHasNoAttributeButIsAccessible { get; set; }
|
||||||
|
int OnlySortableViaFluentApi { get; set; }
|
||||||
|
Comment TopComment { get; set; }
|
||||||
|
Comment FeaturedComment { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using Sieve.Attributes;
|
using Sieve.Attributes;
|
||||||
|
using SieveUnitTests.Abstractions.Entity;
|
||||||
|
|
||||||
namespace SieveUnitTests.Entities
|
namespace SieveUnitTests.Entities
|
||||||
{
|
{
|
||||||
public class BaseEntity
|
public class BaseEntity : IBaseEntity
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
using Sieve.Attributes;
|
using Sieve.Attributes;
|
||||||
|
using SieveUnitTests.Abstractions.Entity;
|
||||||
|
|
||||||
namespace SieveUnitTests.Entities
|
namespace SieveUnitTests.Entities
|
||||||
{
|
{
|
||||||
public class Comment : BaseEntity
|
public class Comment : BaseEntity, IComment
|
||||||
{
|
{
|
||||||
[Sieve(CanFilter = true)]
|
[Sieve(CanFilter = true)]
|
||||||
public string Text { get; set; }
|
public string Text { get; set; }
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using Sieve.Attributes;
|
using Sieve.Attributes;
|
||||||
|
using SieveUnitTests.Abstractions.Entity;
|
||||||
|
|
||||||
namespace SieveUnitTests.Entities
|
namespace SieveUnitTests.Entities
|
||||||
{
|
{
|
||||||
public class Post : BaseEntity
|
public class Post : BaseEntity, IPost
|
||||||
{
|
{
|
||||||
|
|
||||||
[Sieve(CanFilter = true, CanSort = true)]
|
[Sieve(CanFilter = true, CanSort = true)]
|
||||||
|
@ -5,6 +5,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|||||||
using Sieve.Exceptions;
|
using Sieve.Exceptions;
|
||||||
using Sieve.Models;
|
using Sieve.Models;
|
||||||
using Sieve.Services;
|
using Sieve.Services;
|
||||||
|
using SieveUnitTests.Abstractions.Entity;
|
||||||
using SieveUnitTests.Entities;
|
using SieveUnitTests.Entities;
|
||||||
using SieveUnitTests.Services;
|
using SieveUnitTests.Services;
|
||||||
|
|
||||||
@ -564,5 +565,23 @@ namespace SieveUnitTests
|
|||||||
var filteredPosts = result.ToList();
|
var filteredPosts = result.ToList();
|
||||||
Assert.AreEqual(filteredPosts[0].Id, 2);
|
Assert.AreEqual(filteredPosts[0].Id, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void BaseDefinedPropertyMappingSortingWorks_WithCustomName()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Sorts = "-CreateDate"
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _posts);
|
||||||
|
Assert.AreEqual(4, result.Count());
|
||||||
|
|
||||||
|
var posts = result.ToList();
|
||||||
|
Assert.AreEqual(posts[0].Id, 3);
|
||||||
|
Assert.AreEqual(posts[1].Id, 2);
|
||||||
|
Assert.AreEqual(posts[2].Id, 1);
|
||||||
|
Assert.AreEqual(posts[3].Id, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
588
SieveUnitTests/GeneralWithInterfaces.cs
Normal file
588
SieveUnitTests/GeneralWithInterfaces.cs
Normal file
@ -0,0 +1,588 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using Sieve.Exceptions;
|
||||||
|
using Sieve.Models;
|
||||||
|
using Sieve.Services;
|
||||||
|
using SieveUnitTests.Abstractions.Entity;
|
||||||
|
using SieveUnitTests.Entities;
|
||||||
|
using SieveUnitTests.Services;
|
||||||
|
|
||||||
|
namespace SieveUnitTests
|
||||||
|
{
|
||||||
|
[TestClass]
|
||||||
|
public class GeneralWithInterfaces
|
||||||
|
{
|
||||||
|
private readonly SieveProcessor _processor;
|
||||||
|
private readonly IQueryable<IPost> _posts;
|
||||||
|
private readonly IQueryable<Comment> _comments;
|
||||||
|
|
||||||
|
public GeneralWithInterfaces()
|
||||||
|
{
|
||||||
|
_processor = new ApplicationSieveProcessor(new SieveOptionsAccessor(),
|
||||||
|
new SieveCustomSortMethods(),
|
||||||
|
new SieveCustomFilterMethods());
|
||||||
|
|
||||||
|
_posts = new List<IPost>
|
||||||
|
{
|
||||||
|
new Post() {
|
||||||
|
Id = 0,
|
||||||
|
Title = "A",
|
||||||
|
LikeCount = 100,
|
||||||
|
IsDraft = true,
|
||||||
|
CategoryId = null,
|
||||||
|
TopComment = new Comment { Id = 0, Text = "A1" },
|
||||||
|
FeaturedComment = new Comment { Id = 4, Text = "A2" }
|
||||||
|
},
|
||||||
|
new Post() {
|
||||||
|
Id = 1,
|
||||||
|
Title = "B",
|
||||||
|
LikeCount = 50,
|
||||||
|
IsDraft = false,
|
||||||
|
CategoryId = 1,
|
||||||
|
TopComment = new Comment { Id = 3, Text = "B1" },
|
||||||
|
FeaturedComment = new Comment { Id = 5, Text = "B2" }
|
||||||
|
},
|
||||||
|
new Post() {
|
||||||
|
Id = 2,
|
||||||
|
Title = "C",
|
||||||
|
LikeCount = 0,
|
||||||
|
CategoryId = 1,
|
||||||
|
TopComment = new Comment { Id = 2, Text = "C1" },
|
||||||
|
FeaturedComment = new Comment { Id = 6, Text = "C2" }
|
||||||
|
},
|
||||||
|
new Post() {
|
||||||
|
Id = 3,
|
||||||
|
Title = "D",
|
||||||
|
LikeCount = 3,
|
||||||
|
IsDraft = true,
|
||||||
|
CategoryId = 2,
|
||||||
|
TopComment = new Comment { Id = 1, Text = "D1" },
|
||||||
|
FeaturedComment = new Comment { Id = 7, Text = "D2" }
|
||||||
|
},
|
||||||
|
}.AsQueryable();
|
||||||
|
|
||||||
|
_comments = new List<Comment>
|
||||||
|
{
|
||||||
|
new Comment() {
|
||||||
|
Id = 0,
|
||||||
|
DateCreated = DateTimeOffset.UtcNow.AddDays(-20),
|
||||||
|
Text = "This is an old comment."
|
||||||
|
},
|
||||||
|
new Comment() {
|
||||||
|
Id = 1,
|
||||||
|
DateCreated = DateTimeOffset.UtcNow.AddDays(-1),
|
||||||
|
Text = "This is a fairly new comment."
|
||||||
|
},
|
||||||
|
new Comment() {
|
||||||
|
Id = 2,
|
||||||
|
DateCreated = DateTimeOffset.UtcNow,
|
||||||
|
Text = "This is a brand new comment. (Text in braces)"
|
||||||
|
},
|
||||||
|
}.AsQueryable();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ContainsCanBeCaseInsensitive()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "Title@=*a"
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
|
Assert.AreEqual(result.First().Id, 0);
|
||||||
|
Assert.IsTrue(result.Count() == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void NotEqualsCanBeCaseInsensitive()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "Title!=*a"
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
|
Assert.AreEqual(result.First().Id, 1);
|
||||||
|
Assert.IsTrue(result.Count() == 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void ContainsIsCaseSensitive()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "Title@=a",
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
|
Assert.IsTrue(result.Count() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void NotContainsWorks()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "Title!@=D",
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
|
Assert.IsTrue(result.Count() == 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CanFilterBools()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "IsDraft==false"
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
|
Assert.IsTrue(result.Count() == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CanSortBools()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Sorts = "-IsDraft"
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
|
Assert.AreEqual(result.First().Id, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CanFilterNullableInts()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "CategoryId==1"
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
|
Assert.IsTrue(result.Count() == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void EqualsDoesntFailWithNonStringTypes()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "LikeCount==50",
|
||||||
|
};
|
||||||
|
|
||||||
|
Console.WriteLine(model.GetFiltersParsed()[0].Values);
|
||||||
|
Console.WriteLine(model.GetFiltersParsed()[0].Operator);
|
||||||
|
Console.WriteLine(model.GetFiltersParsed()[0].OperatorParsed);
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
|
Assert.AreEqual(result.First().Id, 1);
|
||||||
|
Assert.IsTrue(result.Count() == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CustomFiltersWork()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "Isnew",
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
|
Assert.IsFalse(result.Any(p => p.Id == 0));
|
||||||
|
Assert.IsTrue(result.Count() == 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CustomGenericFiltersWork()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "Latest",
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _comments);
|
||||||
|
|
||||||
|
Assert.IsFalse(result.Any(p => p.Id == 0));
|
||||||
|
Assert.IsTrue(result.Count() == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CustomFiltersWithOperatorsWork()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "HasInTitle==A",
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
|
Assert.IsTrue(result.Any(p => p.Id == 0));
|
||||||
|
Assert.IsTrue(result.Count() == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CustomFiltersMixedWithUsualWork1()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "Isnew,CategoryId==2",
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
|
Assert.IsTrue(result.Any(p => p.Id == 3));
|
||||||
|
Assert.IsTrue(result.Count() == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CustomFiltersMixedWithUsualWork2()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "CategoryId==2,Isnew",
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
|
Assert.IsTrue(result.Any(p => p.Id == 3));
|
||||||
|
Assert.IsTrue(result.Count() == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CustomFiltersOnDifferentSourcesCanShareName()
|
||||||
|
{
|
||||||
|
var postModel = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "CategoryId==2,Isnew",
|
||||||
|
};
|
||||||
|
|
||||||
|
var postResult = _processor.Apply(postModel, _posts);
|
||||||
|
|
||||||
|
Assert.IsTrue(postResult.Any(p => p.Id == 3));
|
||||||
|
Assert.AreEqual(1, postResult.Count());
|
||||||
|
|
||||||
|
var commentModel = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "Isnew",
|
||||||
|
};
|
||||||
|
|
||||||
|
var commentResult = _processor.Apply(commentModel, _comments);
|
||||||
|
|
||||||
|
Assert.IsTrue(commentResult.Any(c => c.Id == 2));
|
||||||
|
Assert.AreEqual(2, commentResult.Count());
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CustomSortsWork()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Sorts = "Popularity",
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
|
Assert.IsFalse(result.First().Id == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CustomGenericSortsWork()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Sorts = "Oldest",
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
|
Assert.IsTrue(result.Last().Id == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void MethodNotFoundExceptionWork()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "does not exist",
|
||||||
|
};
|
||||||
|
|
||||||
|
Assert.ThrowsException<SieveMethodNotFoundException>(() => _processor.Apply(model, _posts));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void IncompatibleMethodExceptionsWork()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "TestComment",
|
||||||
|
};
|
||||||
|
|
||||||
|
Assert.ThrowsException<SieveIncompatibleMethodException>(() => _processor.Apply(model, _posts));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void OrNameFilteringWorks()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "(Title|LikeCount)==3",
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _posts);
|
||||||
|
var entry = result.FirstOrDefault();
|
||||||
|
var resultCount = result.Count();
|
||||||
|
|
||||||
|
Assert.IsNotNull(entry);
|
||||||
|
Assert.AreEqual(1, resultCount);
|
||||||
|
Assert.AreEqual(3, entry.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[DataTestMethod]
|
||||||
|
[DataRow("CategoryId==1,(CategoryId|LikeCount)==50")]
|
||||||
|
[DataRow("(CategoryId|LikeCount)==50,CategoryId==1")]
|
||||||
|
public void CombinedAndOrFilterIndependentOfOrder(string filter)
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = filter,
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _posts);
|
||||||
|
var entry = result.FirstOrDefault();
|
||||||
|
var resultCount = result.Count();
|
||||||
|
|
||||||
|
Assert.IsNotNull(entry);
|
||||||
|
Assert.AreEqual(1, resultCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CombinedAndOrWithSpaceFilteringWorks()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "Title==D, (Title|LikeCount)==3",
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _posts);
|
||||||
|
var entry = result.FirstOrDefault();
|
||||||
|
var resultCount = result.Count();
|
||||||
|
|
||||||
|
Assert.IsNotNull(entry);
|
||||||
|
Assert.AreEqual(1, resultCount);
|
||||||
|
Assert.AreEqual(3, entry.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void OrValueFilteringWorks()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "Title==C|D",
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _posts);
|
||||||
|
Assert.AreEqual(2, result.Count());
|
||||||
|
Assert.IsTrue(result.Any(p => p.Id == 2));
|
||||||
|
Assert.IsTrue(result.Any(p => p.Id == 3));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void OrValueFilteringWorks2()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "Text@=(|)",
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _comments);
|
||||||
|
Assert.AreEqual(1, result.Count());
|
||||||
|
Assert.AreEqual(2, result.FirstOrDefault().Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void NestedFilteringWorks()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "TopComment.Text!@=A",
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _posts);
|
||||||
|
Assert.AreEqual(3, result.Count());
|
||||||
|
var posts = result.ToList();
|
||||||
|
Assert.IsTrue(posts[0].TopComment.Text.Contains("B"));
|
||||||
|
Assert.IsTrue(posts[1].TopComment.Text.Contains("C"));
|
||||||
|
Assert.IsTrue(posts[2].TopComment.Text.Contains("D"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void NestedSortingWorks()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Sorts = "TopComment.Id",
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _posts);
|
||||||
|
Assert.AreEqual(4, result.Count());
|
||||||
|
var posts = result.ToList();
|
||||||
|
Assert.AreEqual(posts[0].Id, 0);
|
||||||
|
Assert.AreEqual(posts[1].Id, 3);
|
||||||
|
Assert.AreEqual(posts[2].Id, 2);
|
||||||
|
Assert.AreEqual(posts[3].Id, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void NestedFilteringWithIdenticTypesWorks()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "(topc|featc)@=*2",
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _posts);
|
||||||
|
Assert.AreEqual(4, result.Count());
|
||||||
|
|
||||||
|
model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "(topc|featc)@=*B",
|
||||||
|
};
|
||||||
|
|
||||||
|
result = _processor.Apply(model, _posts);
|
||||||
|
Assert.AreEqual(1, result.Count());
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void FilteringNullsWorks()
|
||||||
|
{
|
||||||
|
var posts = new List<Post>
|
||||||
|
{
|
||||||
|
new Post() {
|
||||||
|
Id = 1,
|
||||||
|
Title = null,
|
||||||
|
LikeCount = 0,
|
||||||
|
IsDraft = false,
|
||||||
|
CategoryId = null,
|
||||||
|
TopComment = null,
|
||||||
|
FeaturedComment = null
|
||||||
|
},
|
||||||
|
}.AsQueryable();
|
||||||
|
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "FeaturedComment.Text!@=Some value",
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, posts);
|
||||||
|
Assert.AreEqual(0, result.Count());
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void SortingNullsWorks()
|
||||||
|
{
|
||||||
|
var posts = new List<Post>
|
||||||
|
{
|
||||||
|
new Post() {
|
||||||
|
Id = 1,
|
||||||
|
Title = null,
|
||||||
|
LikeCount = 0,
|
||||||
|
IsDraft = false,
|
||||||
|
CategoryId = null,
|
||||||
|
TopComment = new Comment { Id = 1 },
|
||||||
|
FeaturedComment = null
|
||||||
|
},
|
||||||
|
new Post() {
|
||||||
|
Id = 2,
|
||||||
|
Title = null,
|
||||||
|
LikeCount = 0,
|
||||||
|
IsDraft = false,
|
||||||
|
CategoryId = null,
|
||||||
|
TopComment = null,
|
||||||
|
FeaturedComment = null
|
||||||
|
},
|
||||||
|
}.AsQueryable();
|
||||||
|
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Sorts = "TopComment.Id",
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, posts);
|
||||||
|
Assert.AreEqual(2, result.Count());
|
||||||
|
var sortedPosts = result.ToList();
|
||||||
|
Assert.AreEqual(sortedPosts[0].Id, 2);
|
||||||
|
Assert.AreEqual(sortedPosts[1].Id, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void FilteringOnNullWorks()
|
||||||
|
{
|
||||||
|
var posts = new List<Post>
|
||||||
|
{
|
||||||
|
new Post() {
|
||||||
|
Id = 1,
|
||||||
|
Title = null,
|
||||||
|
LikeCount = 0,
|
||||||
|
IsDraft = false,
|
||||||
|
CategoryId = null,
|
||||||
|
TopComment = null,
|
||||||
|
FeaturedComment = null
|
||||||
|
},
|
||||||
|
new Post() {
|
||||||
|
Id = 2,
|
||||||
|
Title = null,
|
||||||
|
LikeCount = 0,
|
||||||
|
IsDraft = false,
|
||||||
|
CategoryId = null,
|
||||||
|
TopComment = null,
|
||||||
|
FeaturedComment = new Comment { Id = 1, Text = null }
|
||||||
|
},
|
||||||
|
}.AsQueryable();
|
||||||
|
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Filters = "FeaturedComment.Text==null",
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, posts);
|
||||||
|
Assert.AreEqual(1, result.Count());
|
||||||
|
var filteredPosts = result.ToList();
|
||||||
|
Assert.AreEqual(filteredPosts[0].Id, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void BaseDefinedPropertyMappingSortingWorks_WithCustomName()
|
||||||
|
{
|
||||||
|
var model = new SieveModel()
|
||||||
|
{
|
||||||
|
Sorts = "-CreateDate"
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _posts);
|
||||||
|
Assert.AreEqual(4, result.Count());
|
||||||
|
|
||||||
|
var posts = result.ToList();
|
||||||
|
Assert.AreEqual(posts[0].Id, 3);
|
||||||
|
Assert.AreEqual(posts[1].Id, 2);
|
||||||
|
Assert.AreEqual(posts[2].Id, 1);
|
||||||
|
Assert.AreEqual(posts[3].Id, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Sieve.Models;
|
using Sieve.Models;
|
||||||
using Sieve.Services;
|
using Sieve.Services;
|
||||||
|
using SieveUnitTests.Abstractions.Entity;
|
||||||
using SieveUnitTests.Entities;
|
using SieveUnitTests.Entities;
|
||||||
|
|
||||||
namespace SieveUnitTests.Services
|
namespace SieveUnitTests.Services
|
||||||
@ -39,6 +40,39 @@ namespace SieveUnitTests.Services
|
|||||||
.CanFilter()
|
.CanFilter()
|
||||||
.HasName("featc");
|
.HasName("featc");
|
||||||
|
|
||||||
|
mapper
|
||||||
|
.Property<Post>(p => p.DateCreated)
|
||||||
|
.CanSort()
|
||||||
|
.HasName("CreateDate");
|
||||||
|
|
||||||
|
// interfaces
|
||||||
|
mapper.Property<IPost>(p => p.ThisHasNoAttributeButIsAccessible)
|
||||||
|
.CanSort()
|
||||||
|
.CanFilter()
|
||||||
|
.HasName("shortname");
|
||||||
|
|
||||||
|
mapper.Property<IPost>(p => p.TopComment.Text)
|
||||||
|
.CanFilter();
|
||||||
|
|
||||||
|
mapper.Property<IPost>(p => p.TopComment.Id)
|
||||||
|
.CanSort();
|
||||||
|
|
||||||
|
mapper.Property<IPost>(p => p.OnlySortableViaFluentApi)
|
||||||
|
.CanSort();
|
||||||
|
|
||||||
|
mapper.Property<IPost>(p => p.TopComment.Text)
|
||||||
|
.CanFilter()
|
||||||
|
.HasName("topc");
|
||||||
|
|
||||||
|
mapper.Property<IPost>(p => p.FeaturedComment.Text)
|
||||||
|
.CanFilter()
|
||||||
|
.HasName("featc");
|
||||||
|
|
||||||
|
mapper
|
||||||
|
.Property<IPost>(p => p.DateCreated)
|
||||||
|
.CanSort()
|
||||||
|
.HasName("CreateDate");
|
||||||
|
|
||||||
return mapper;
|
return mapper;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Sieve.Services;
|
using Sieve.Services;
|
||||||
|
using SieveUnitTests.Abstractions.Entity;
|
||||||
using SieveUnitTests.Entities;
|
using SieveUnitTests.Entities;
|
||||||
|
|
||||||
namespace SieveUnitTests.Services
|
namespace SieveUnitTests.Services
|
||||||
@ -38,5 +39,31 @@ namespace SieveUnitTests.Services
|
|||||||
var result = source.Where(c => c.DateCreated > DateTimeOffset.UtcNow.AddDays(-14));
|
var result = source.Where(c => c.DateCreated > DateTimeOffset.UtcNow.AddDays(-14));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IQueryable<IPost> IsNew(IQueryable<IPost> source, string op, string[] values)
|
||||||
|
{
|
||||||
|
var result = source.Where(p => p.LikeCount < 100);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IQueryable<IPost> HasInTitle(IQueryable<IPost> source, string op, string[] values)
|
||||||
|
{
|
||||||
|
var result = source.Where(p => p.Title.Contains(values[0]));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IQueryable<IComment> IsNew(IQueryable<IComment> source, string op, string[] values)
|
||||||
|
{
|
||||||
|
var result = source.Where(c => c.DateCreated > DateTimeOffset.UtcNow.AddDays(-2));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IQueryable<IComment> TestComment(IQueryable<IComment> source, string op, string[] values)
|
||||||
|
{
|
||||||
|
return source;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Sieve.Services;
|
using Sieve.Services;
|
||||||
|
using SieveUnitTests.Abstractions.Entity;
|
||||||
using SieveUnitTests.Entities;
|
using SieveUnitTests.Entities;
|
||||||
|
|
||||||
namespace SieveUnitTests.Services
|
namespace SieveUnitTests.Services
|
||||||
@ -17,7 +18,18 @@ namespace SieveUnitTests.Services
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IQueryable<T> Oldest<T>(IQueryable<T> source, bool useThenBy, bool desc) where T : BaseEntity
|
public IQueryable<IPost> Popularity(IQueryable<IPost> source, bool useThenBy, bool desc)
|
||||||
|
{
|
||||||
|
var result = useThenBy ?
|
||||||
|
((IOrderedQueryable<IPost>)source).ThenBy(p => p.LikeCount) :
|
||||||
|
source.OrderBy(p => p.LikeCount)
|
||||||
|
.ThenBy(p => p.CommentCount)
|
||||||
|
.ThenBy(p => p.DateCreated);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IQueryable<T> Oldest<T>(IQueryable<T> source, bool useThenBy, bool desc) where T : IBaseEntity
|
||||||
{
|
{
|
||||||
var result = useThenBy ?
|
var result = useThenBy ?
|
||||||
((IOrderedQueryable<T>)source).ThenByDescending(p => p.DateCreated) :
|
((IOrderedQueryable<T>)source).ThenByDescending(p => p.DateCreated) :
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Sieve.Models;
|
using Sieve.Models;
|
||||||
|
|
||||||
namespace SieveUnitTests
|
namespace SieveUnitTests.Services
|
||||||
{
|
{
|
||||||
public class SieveOptionsAccessor : IOptions<SieveOptions>
|
public class SieveOptionsAccessor : IOptions<SieveOptions>
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user