diff --git a/Sieve.Sample/Sieve.Sample.csproj b/Sieve.Sample/Sieve.Sample.csproj index 8aca6b9..a8c1f83 100644 --- a/Sieve.Sample/Sieve.Sample.csproj +++ b/Sieve.Sample/Sieve.Sample.csproj @@ -1,16 +1,16 @@  - netcoreapp3.1 + net6.0 - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + diff --git a/Sieve/Extensions/OrderByDynamic.cs b/Sieve/Extensions/LinqExtensions.cs similarity index 52% rename from Sieve/Extensions/OrderByDynamic.cs rename to Sieve/Extensions/LinqExtensions.cs index aea83a8..7d3848a 100644 --- a/Sieve/Extensions/OrderByDynamic.cs +++ b/Sieve/Extensions/LinqExtensions.cs @@ -1,21 +1,76 @@ using System; using System.Linq; using System.Linq.Expressions; -using System.Reflection; +using Sieve.Exceptions; namespace Sieve.Extensions { public static partial class LinqExtensions { + internal static Expression GeneratePropertyAccess + ( + this Expression parameterExpression, + string propertyName + ) + { + var propertyAccessor = parameterExpression; + + try + { + propertyAccessor = Expression.PropertyOrField(propertyAccessor, propertyName); + } + catch (ArgumentException) + { + // propertyName is not a direct property of field of propertyAccessor expression's type. + // when propertyAccessor.Type is directly an interface, say typeof(ISomeEntity) and ISomeEntity is interfaced to IBaseEntity + // in which `propertyName` is defined in the first place. + // To solve this, search `propertyName` in all other implemented interfaces + + var possibleInterfaceType = propertyAccessor.Type; + + if (!possibleInterfaceType.IsInterface) + throw; + + // get all implemented interface types + var implementedInterfaces = possibleInterfaceType.GetInterfaces(); + + try + { + // search propertyName in all interfaces + var interfacedExpression = implementedInterfaces + .Where + ( + implementedInterface => implementedInterface + .GetProperties() + .Any(info => info.Name == propertyName) + ) + .Select(implementedInterface => Expression.TypeAs(propertyAccessor, implementedInterface)) + .SingleOrDefault(); + + if (interfacedExpression != null) + propertyAccessor = Expression.PropertyOrField(interfacedExpression, propertyName); + } + catch (InvalidOperationException ioe) + { + throw new SieveException + ( + $"{propertyName} is repeated in interface hierarchy. Try renaming.", + ioe + ); + } + } + + return propertyAccessor; + } + public static IQueryable OrderByDynamic( this IQueryable source, string fullPropertyName, - PropertyInfo propertyInfo, bool desc, bool useThenBy, bool disableNullableTypeExpression = false) { - var lambda = GenerateLambdaWithSafeMemberAccess(fullPropertyName, propertyInfo, disableNullableTypeExpression); + var lambda = GenerateLambdaWithSafeMemberAccess(fullPropertyName, disableNullableTypeExpression); var command = desc ? (useThenBy ? "ThenByDescending" : "OrderByDescending") @@ -34,7 +89,6 @@ namespace Sieve.Extensions private static Expression> GenerateLambdaWithSafeMemberAccess ( string fullPropertyName, - PropertyInfo propertyInfo, bool disableNullableTypeExpression ) { @@ -44,15 +98,7 @@ namespace Sieve.Extensions foreach (var name in fullPropertyName.Split('.')) { - try - { - 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); - } + propertyValue = propertyValue.GeneratePropertyAccess(name); if (propertyValue.Type.IsNullable() && !disableNullableTypeExpression) { diff --git a/Sieve/Services/SieveProcessor.cs b/Sieve/Services/SieveProcessor.cs index 5b36ea1..2f72473 100644 --- a/Sieve/Services/SieveProcessor.cs +++ b/Sieve/Services/SieveProcessor.cs @@ -289,7 +289,7 @@ namespace Sieve.Services var names = fullPropertyName.Split('.'); for (var i = 0; i < names.Length; i++) { - propertyValue = Expression.PropertyOrField(propertyValue, names[i]); + propertyValue = propertyValue.GeneratePropertyAccess(names[i]); if (i != names.Length - 1 && propertyValue.Type.IsNullable()) { @@ -368,7 +368,7 @@ namespace Sieve.Services if (property != null) { - result = result.OrderByDynamic(fullName, property, sortTerm.Descending, useThenBy, Options.Value.DisableNullableTypeExpressionForSorting); + result = result.OrderByDynamic(fullName, sortTerm.Descending, useThenBy, Options.Value.DisableNullableTypeExpressionForSorting); } else { diff --git a/Sieve/Sieve.csproj b/Sieve/Sieve.csproj index 78b51b0..b7d73a5 100644 --- a/Sieve/Sieve.csproj +++ b/Sieve/Sieve.csproj @@ -22,9 +22,9 @@ - - - + + + diff --git a/SieveUnitTests/Abstractions/Entity/IBaseEntity.cs b/SieveUnitTests/Abstractions/Entity/IBaseEntity.cs index ce1edf9..6c1b16b 100644 --- a/SieveUnitTests/Abstractions/Entity/IBaseEntity.cs +++ b/SieveUnitTests/Abstractions/Entity/IBaseEntity.cs @@ -1,8 +1,9 @@ using System; +using SieveUnitTests.Abstractions.Strategy; namespace SieveUnitTests.Abstractions.Entity { - public interface IBaseEntity + public interface IBaseEntity: ISupportSoftDelete { int Id { get; set; } DateTimeOffset DateCreated { get; set; } diff --git a/SieveUnitTests/Abstractions/Entity/IPost.cs b/SieveUnitTests/Abstractions/Entity/IPost.cs index b05d365..5453c5e 100644 --- a/SieveUnitTests/Abstractions/Entity/IPost.cs +++ b/SieveUnitTests/Abstractions/Entity/IPost.cs @@ -1,9 +1,10 @@ using Sieve.Attributes; +using SieveUnitTests.Abstractions.Strategy; using SieveUnitTests.Entities; namespace SieveUnitTests.Abstractions.Entity { - public interface IPost: IBaseEntity + public interface IPost: IBaseEntity, IAudit { [Sieve(CanFilter = true, CanSort = true)] string Title { get; set; } diff --git a/SieveUnitTests/Abstractions/Entity/SieveConfigurationForIPost.cs b/SieveUnitTests/Abstractions/Entity/SieveConfigurationForIPost.cs index f6270ed..bfa2f45 100644 --- a/SieveUnitTests/Abstractions/Entity/SieveConfigurationForIPost.cs +++ b/SieveUnitTests/Abstractions/Entity/SieveConfigurationForIPost.cs @@ -4,13 +4,17 @@ namespace SieveUnitTests.Abstractions.Entity { public class SieveConfigurationForIPost : ISieveConfiguration { - public void Configure(SievePropertyMapper mapper) + public static void ConfigureStatic(SievePropertyMapper mapper) { + mapper + .Property(p => p.Id) + .CanSort(); + mapper.Property(p => p.ThisHasNoAttributeButIsAccessible) .CanSort() .CanFilter() .HasName("shortname"); - + mapper.Property(p => p.TopComment.Text) .CanFilter(); @@ -32,6 +36,21 @@ namespace SieveUnitTests.Abstractions.Entity .Property(p => p.DateCreated) .CanSort() .HasName("CreateDate"); + + mapper + .Property(post => post.DeletedBy) + .CanSort() + .HasName("DeletedBy"); + + mapper + .Property(post => post.UpdatedBy) + .CanFilter() + .HasName("UpdatedBy"); + } + + public void Configure(SievePropertyMapper mapper) + { + ConfigureStatic(mapper); } } } diff --git a/SieveUnitTests/Abstractions/Strategy/IAudit.cs b/SieveUnitTests/Abstractions/Strategy/IAudit.cs new file mode 100644 index 0000000..a67913f --- /dev/null +++ b/SieveUnitTests/Abstractions/Strategy/IAudit.cs @@ -0,0 +1,12 @@ +using System; + +namespace SieveUnitTests.Abstractions.Strategy +{ + public interface IAudit + { + string CreatedBy { get; } + DateTime? CreatedAt { get; } + string UpdatedBy { get; } + DateTime? UpdatedAt { get; } + } +} diff --git a/SieveUnitTests/Abstractions/Strategy/ISupportSoftDelete.cs b/SieveUnitTests/Abstractions/Strategy/ISupportSoftDelete.cs new file mode 100644 index 0000000..23759c4 --- /dev/null +++ b/SieveUnitTests/Abstractions/Strategy/ISupportSoftDelete.cs @@ -0,0 +1,10 @@ +using System; + +namespace SieveUnitTests.Abstractions.Strategy +{ + public interface ISupportSoftDelete + { + string DeletedBy { get; } + DateTime? DeletedAt { get; } + } +} diff --git a/SieveUnitTests/Entities/BaseEntity.cs b/SieveUnitTests/Entities/BaseEntity.cs index 6660033..c2a3d1c 100644 --- a/SieveUnitTests/Entities/BaseEntity.cs +++ b/SieveUnitTests/Entities/BaseEntity.cs @@ -10,5 +10,8 @@ namespace SieveUnitTests.Entities [Sieve(CanFilter = true, CanSort = true)] public DateTimeOffset DateCreated { get; set; } = DateTimeOffset.UtcNow; + + public string DeletedBy { get; set; } + public DateTime? DeletedAt { get; set; } } } diff --git a/SieveUnitTests/Entities/Post.cs b/SieveUnitTests/Entities/Post.cs index aa931e9..d93867c 100644 --- a/SieveUnitTests/Entities/Post.cs +++ b/SieveUnitTests/Entities/Post.cs @@ -30,5 +30,10 @@ namespace SieveUnitTests.Entities public Comment TopComment { get; set; } public Comment FeaturedComment { get; set; } + + public string CreatedBy { get; set; } + public DateTime? CreatedAt { get; set; } + public string UpdatedBy { get; set; } + public DateTime? UpdatedAt { get; set; } } } diff --git a/SieveUnitTests/Entities/SieveConfigurationForPost.cs b/SieveUnitTests/Entities/SieveConfigurationForPost.cs index 8a02178..e3d7782 100644 --- a/SieveUnitTests/Entities/SieveConfigurationForPost.cs +++ b/SieveUnitTests/Entities/SieveConfigurationForPost.cs @@ -4,8 +4,12 @@ namespace SieveUnitTests.Entities { public class SieveConfigurationForPost : ISieveConfiguration { - public void Configure(SievePropertyMapper mapper) + public static void ConfigureStatic(SievePropertyMapper mapper) { + mapper + .Property(p => p.Id) + .CanSort(); + mapper.Property(p => p.ThisHasNoAttributeButIsAccessible) .CanSort() .CanFilter() @@ -32,6 +36,21 @@ namespace SieveUnitTests.Entities .Property(p => p.DateCreated) .CanSort() .HasName("CreateDate"); + + mapper + .Property(post => post.DeletedBy) + .CanSort() + .HasName("DeletedBy"); + + mapper + .Property(post => post.UpdatedBy) + .CanFilter() + .HasName("UpdatedBy"); + } + + public void Configure(SievePropertyMapper mapper) + { + ConfigureStatic(mapper); } } } diff --git a/SieveUnitTests/General.cs b/SieveUnitTests/General.cs index 886ce50..1dae88a 100644 --- a/SieveUnitTests/General.cs +++ b/SieveUnitTests/General.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using Sieve.Exceptions; using Sieve.Models; -using Sieve.Services; using SieveUnitTests.Entities; using SieveUnitTests.Services; using Xunit; @@ -11,28 +10,14 @@ using Xunit.Abstractions; namespace SieveUnitTests { - public class General + public class General: TestBase { - private readonly ITestOutputHelper _testOutputHelper; - private readonly SieveProcessor _processor; - private readonly SieveProcessor _nullableProcessor; private readonly IQueryable _posts; private readonly IQueryable _comments; public General(ITestOutputHelper testOutputHelper) + : base(testOutputHelper) { - var nullableAccessor = new SieveOptionsAccessor(); - nullableAccessor.Value.IgnoreNullsOnNotEqual = false; - - _testOutputHelper = testOutputHelper; - _processor = new ApplicationSieveProcessor(new SieveOptionsAccessor(), - new SieveCustomSortMethods(), - new SieveCustomFilterMethods()); - - _nullableProcessor = new ApplicationSieveProcessor(nullableAccessor, - new SieveCustomSortMethods(), - new SieveCustomFilterMethods()); - _posts = new List { new Post @@ -77,6 +62,16 @@ namespace SieveUnitTests new Post { Id = 4, + Title = "E", + LikeCount = 5, + IsDraft = false, + CategoryId = null, + TopComment = new Comment { Id = 4, Text = "E1" }, + UpdatedBy = "You" + }, + new Post + { + Id = 5, Title = "Yen", LikeCount = 5, IsDraft = true, @@ -117,12 +112,15 @@ namespace SieveUnitTests Filters = "Title@=*a" }; - var result = _processor.Apply(model, _posts); + foreach (var sieveProcessor in GetProcessors()) + { + var result = sieveProcessor.Apply(model, _posts); - Assert.Equal(0, result.First().Id); - Assert.True(result.Count() == 1); + Assert.Equal(0, result.First().Id); + Assert.True(result.Count() == 1); + } } - + [Fact] public void NotEqualsCanBeCaseInsensitive() { @@ -131,10 +129,13 @@ namespace SieveUnitTests Filters = "Title!=*a" }; - var result = _processor.Apply(model, _posts); + foreach (var sieveProcessor in GetProcessors()) + { + var result = sieveProcessor.Apply(model, _posts); - Assert.Equal(1, result.First().Id); - Assert.True(result.Count() == 4); + Assert.Equal(1, result.First().Id); + Assert.True(result.Count() == _posts.Count(post => !post.Title.Contains("a", StringComparison.OrdinalIgnoreCase))); + } } [Fact] @@ -145,14 +146,17 @@ namespace SieveUnitTests Filters = "Title_-=n" }; - _testOutputHelper.WriteLine(model.GetFiltersParsed()[0].Values.ToString()); - _testOutputHelper.WriteLine(model.GetFiltersParsed()[0].Operator); - _testOutputHelper.WriteLine(model.GetFiltersParsed()[0].OperatorParsed.ToString()); + TestOutputHelper.WriteLine(model.GetFiltersParsed()[0].Values.ToString()); + TestOutputHelper.WriteLine(model.GetFiltersParsed()[0].Operator); + TestOutputHelper.WriteLine(model.GetFiltersParsed()[0].OperatorParsed.ToString()); - var result = _processor.Apply(model, _posts); + foreach (var sieveProcessor in GetProcessors()) + { + var result = sieveProcessor.Apply(model, _posts); - Assert.Equal(4, result.First().Id); - Assert.True(result.Count() == 1); + Assert.Equal(_posts.First(post => post.Title.EndsWith("n")).Id, result.First().Id); + Assert.True(result.Count() == 1); + } } [Fact] @@ -163,27 +167,30 @@ namespace SieveUnitTests Filters = "Title_-=*N" }; - _testOutputHelper.WriteLine(model.GetFiltersParsed()[0].Values.ToString()); - _testOutputHelper.WriteLine(model.GetFiltersParsed()[0].Operator); - _testOutputHelper.WriteLine(model.GetFiltersParsed()[0].OperatorParsed.ToString()); + TestOutputHelper.WriteLine(model.GetFiltersParsed()[0].Values.ToString()); + TestOutputHelper.WriteLine(model.GetFiltersParsed()[0].Operator); + TestOutputHelper.WriteLine(model.GetFiltersParsed()[0].OperatorParsed.ToString()); - var result = _processor.Apply(model, _posts); + foreach (var sieveProcessor in GetProcessors()) + { + var result = sieveProcessor.Apply(model, _posts); - Assert.Equal(4, result.First().Id); - Assert.True(result.Count() == 1); + Assert.Equal(_posts.First(post => post.Title.EndsWith("N", StringComparison.OrdinalIgnoreCase)).Id, result.First().Id); + Assert.True(result.Count() == 1); + } } [Fact] public void ContainsIsCaseSensitive() { - var model = new SieveModel + var model = new SieveModel { Filters = "Title@=a", }; + + foreach (var sieveProcessor in GetProcessors()) { - Filters = "Title@=a", - }; + var result = sieveProcessor.Apply(model, _posts); - var result = _processor.Apply(model, _posts); - - Assert.True(!result.Any()); + Assert.True(!result.Any()); + } } [Fact] @@ -194,65 +201,70 @@ namespace SieveUnitTests Filters = "Title!@=D", }; - var result = _processor.Apply(model, _posts); + foreach (var sieveProcessor in GetProcessors()) + { + var result = sieveProcessor.Apply(model, _posts); - Assert.True(result.Count() == 4); + Assert.True(result.Count() == _posts.Count(post => !post.Title.Contains("D"))); + } } [Fact] public void CanFilterBools() { - var model = new SieveModel + var model = new SieveModel { Filters = "IsDraft==false" }; + + foreach (var sieveProcessor in GetProcessors()) { - Filters = "IsDraft==false" - }; + var result = sieveProcessor.Apply(model, _posts); - var result = _processor.Apply(model, _posts); - - Assert.True(result.Count() == 2); + Assert.True(result.Count() == _posts.Count(post => !post.IsDraft)); + } } [Fact] public void CanSortBools() { - var model = new SieveModel + var model = new SieveModel { Sorts = "-IsDraft" }; + + foreach (var sieveProcessor in GetProcessors()) { - Sorts = "-IsDraft" - }; + var result = sieveProcessor.Apply(model, _posts); - var result = _processor.Apply(model, _posts); - - Assert.Equal(0, result.First().Id); + Assert.Equal(0, result.First().Id); + } } [Fact] public void CanFilterNullableInts() { - var model = new SieveModel + var model = new SieveModel { Filters = "CategoryId==1" }; + + foreach (var sieveProcessor in GetProcessors()) { - Filters = "CategoryId==1" - }; + var result = sieveProcessor.Apply(model, _posts); - var result = _processor.Apply(model, _posts); - var nullableResult = _nullableProcessor.Apply(model, _posts); - - Assert.True(result.Count() == 2); - Assert.True(nullableResult.Count() == 2); + Assert.True(result.Count() == _posts.Count(post => post.CategoryId == 1)); + } } [Fact] public void CanFilterNullableIntsWithNotEqual() { - var model = new SieveModel() + var model = new SieveModel() { Filters = "CategoryId!=1" }; + + foreach (var sieveProcessor in GetProcessors()) { - Filters = "CategoryId!=1" - }; + var result = sieveProcessor.Apply(model, _posts); - var result = _processor.Apply(model, _posts); - var nullableResult = _nullableProcessor.Apply(model, _posts); - - Assert.True(result.Count() == 2); - Assert.True(nullableResult.Count() == 3); + Assert.Equal + ( + (sieveProcessor as ApplicationSieveProcessor)?.ExposedOptions.IgnoreNullsOnNotEqual ?? true + ? _posts.Count(post => post.CategoryId != null && post.CategoryId != 1) + : _posts.Count(post => post.CategoryId != 1), + result.Count() + ); + } } [Theory] @@ -267,97 +279,100 @@ namespace SieveUnitTests Filters = filter }; - var result = _processor.Apply(model, _comments); + foreach (var sieveProcessor in GetProcessors()) + { + var result = sieveProcessor.Apply(model, _comments); - Assert.True(result.Count() == 1); + Assert.True(result.Count() == 1); + } } [Fact] public void EqualsDoesntFailWithNonStringTypes() { - var model = new SieveModel + var model = new SieveModel { Filters = "LikeCount==50", }; + + TestOutputHelper.WriteLine(model.GetFiltersParsed()[0].Values.ToString()); + TestOutputHelper.WriteLine(model.GetFiltersParsed()[0].Operator); + TestOutputHelper.WriteLine(model.GetFiltersParsed()[0].OperatorParsed.ToString()); + + foreach (var sieveProcessor in GetProcessors()) { - Filters = "LikeCount==50", - }; + var result = sieveProcessor.Apply(model, _posts); - _testOutputHelper.WriteLine(model.GetFiltersParsed()[0].Values.ToString()); - _testOutputHelper.WriteLine(model.GetFiltersParsed()[0].Operator); - _testOutputHelper.WriteLine(model.GetFiltersParsed()[0].OperatorParsed.ToString()); - - var result = _processor.Apply(model, _posts); - - Assert.Equal(1, result.First().Id); - Assert.True(result.Count() == 1); + Assert.Equal(1, result.First().Id); + Assert.True(result.Count() == 1); + } } [Fact] public void CustomFiltersWork() { - var model = new SieveModel + var model = new SieveModel { Filters = "Isnew", }; + + foreach (var sieveProcessor in GetProcessors()) { - Filters = "Isnew", - }; + var result = sieveProcessor.Apply(model, _posts); - var result = _processor.Apply(model, _posts); - - Assert.False(result.Any(p => p.Id == 0)); - Assert.True(result.Count() == 4); + Assert.False(result.Any(p => p.Id == 0)); + Assert.True(result.Count() == _posts.Count(SieveCustomFilterMethods.IsNewFilterForPost)); + } } [Fact] public void CustomGenericFiltersWork() { - var model = new SieveModel + var model = new SieveModel { Filters = "Latest", }; + + foreach (var sieveProcessor in GetProcessors()) { - Filters = "Latest", - }; + var result = sieveProcessor.Apply(model, _comments); - var result = _processor.Apply(model, _comments); - - Assert.False(result.Any(p => p.Id == 0)); - Assert.True(result.Count() == 2); + Assert.False(result.Any(p => p.Id == 0)); + Assert.True(result.Count() == 2); + } } [Fact] public void CustomFiltersWithOperatorsWork() { - var model = new SieveModel + var model = new SieveModel { Filters = "HasInTitle==A", }; + + foreach (var sieveProcessor in GetProcessors()) { - Filters = "HasInTitle==A", - }; + var result = sieveProcessor.Apply(model, _posts); - var result = _processor.Apply(model, _posts); - - Assert.True(result.Any(p => p.Id == 0)); - Assert.True(result.Count() == 1); + Assert.True(result.Any(p => p.Id == 0)); + Assert.True(result.Count() == 1); + } } [Fact] public void CustomFiltersMixedWithUsualWork1() { - var model = new SieveModel + var model = new SieveModel { Filters = "Isnew,CategoryId==2", }; + + foreach (var sieveProcessor in GetProcessors()) { - Filters = "Isnew,CategoryId==2", - }; + var result = sieveProcessor.Apply(model, _posts); - var result = _processor.Apply(model, _posts); - - Assert.True(result.Any(p => p.Id == 3)); - Assert.True(result.Count() == 1); + Assert.True(result.Any(p => p.Id == 3)); + Assert.True(result.Count() == 1); + } } [Fact] public void CustomFiltersMixedWithUsualWork2() { - var model = new SieveModel + var model = new SieveModel { Filters = "CategoryId==2,Isnew", }; + + foreach (var sieveProcessor in GetProcessors()) { - Filters = "CategoryId==2,Isnew", - }; + var result = sieveProcessor.Apply(model, _posts); - var result = _processor.Apply(model, _posts); - - Assert.True(result.Any(p => p.Id == 3)); - Assert.True(result.Count() == 1); + Assert.True(result.Any(p => p.Id == 3)); + Assert.True(result.Count() == 1); + } } [Fact] @@ -368,85 +383,91 @@ namespace SieveUnitTests Filters = "CategoryId==2,Isnew", }; - var postResult = _processor.Apply(postModel, _posts); + foreach (var sieveProcessor in GetProcessors()) + { + var postResult = sieveProcessor.Apply(postModel, _posts); - Assert.True(postResult.Any(p => p.Id == 3)); - Assert.Equal(1, postResult.Count()); + Assert.True(postResult.Any(p => p.Id == 3)); + Assert.Equal(1, postResult.Count()); + } var commentModel = new SieveModel { Filters = "Isnew", }; - var commentResult = _processor.Apply(commentModel, _comments); + foreach (var sieveProcessor in GetProcessors()) + { + var commentResult = sieveProcessor.Apply(commentModel, _comments); - Assert.True(commentResult.Any(c => c.Id == 2)); - Assert.Equal(2, commentResult.Count()); + Assert.True(commentResult.Any(c => c.Id == 2)); + Assert.Equal(2, commentResult.Count()); + } } [Fact] public void CustomSortsWork() { - var model = new SieveModel + var model = new SieveModel { Sorts = "Popularity", }; + + foreach (var sieveProcessor in GetProcessors()) { - Sorts = "Popularity", - }; + var result = sieveProcessor.Apply(model, _posts); - var result = _processor.Apply(model, _posts); - - Assert.False(result.First().Id == 0); + Assert.False(result.First().Id == 0); + } } [Fact] public void CustomGenericSortsWork() { - var model = new SieveModel + var model = new SieveModel { Sorts = "Oldest", }; + + foreach (var sieveProcessor in GetProcessors()) { - Sorts = "Oldest", - }; + var result = sieveProcessor.Apply(model, _posts); - var result = _processor.Apply(model, _posts); - - Assert.True(result.Last().Id == 0); + Assert.True(result.Last().Id == 0); + } } [Fact] public void MethodNotFoundExceptionWork() { - var model = new SieveModel - { - Filters = "does not exist", - }; + var model = new SieveModel { Filters = "does not exist", }; - Assert.Throws(() => _processor.Apply(model, _posts)); + foreach (var sieveProcessor in GetProcessors()) + { + Assert.Throws(() => sieveProcessor.Apply(model, _posts)); + } } [Fact] public void IncompatibleMethodExceptionsWork() { - var model = new SieveModel - { - Filters = "TestComment", - }; + var model = new SieveModel { Filters = "TestComment", }; - Assert.Throws(() => _processor.Apply(model, _posts)); + foreach (var sieveProcessor in GetProcessors()) + { + Assert.Throws(() => sieveProcessor.Apply(model, _posts)); + } } [Fact] public void OrNameFilteringWorks() { - var model = new SieveModel + var model = new SieveModel { Filters = "(Title|LikeCount)==3", }; + + foreach (var sieveProcessor in GetProcessors()) { - Filters = "(Title|LikeCount)==3", - }; + var result = sieveProcessor.Apply(model, _posts); + var entry = result.FirstOrDefault(); + var resultCount = result.Count(); - var result = _processor.Apply(model, _posts); - var entry = result.FirstOrDefault(); - var resultCount = result.Count(); - - Assert.NotNull(entry); - Assert.Equal(1, resultCount); - Assert.Equal(3, entry.Id); + Assert.NotNull(entry); + Assert.Equal(1, resultCount); + Assert.Equal(3, entry.Id); + } } [Theory] @@ -454,96 +475,105 @@ namespace SieveUnitTests [InlineData("(CategoryId|LikeCount)==50,CategoryId==1")] public void CombinedAndOrFilterIndependentOfOrder(string filter) { - var model = new SieveModel + var model = new SieveModel { Filters = filter, }; + + foreach (var sieveProcessor in GetProcessors()) { - Filters = filter, - }; + var result = sieveProcessor.Apply(model, _posts); + var entry = result.FirstOrDefault(); + var resultCount = result.Count(); - var result = _processor.Apply(model, _posts); - var entry = result.FirstOrDefault(); - var resultCount = result.Count(); - - Assert.NotNull(entry); - Assert.Equal(1, resultCount); + Assert.NotNull(entry); + Assert.Equal(1, resultCount); + } } [Fact] public void CombinedAndOrWithSpaceFilteringWorks() { - var model = new SieveModel + var model = new SieveModel { Filters = "Title==D, (Title|LikeCount)==3", }; + + foreach (var sieveProcessor in GetProcessors()) { - Filters = "Title==D, (Title|LikeCount)==3", - }; + var result = sieveProcessor.Apply(model, _posts); + var entry = result.FirstOrDefault(); + var resultCount = result.Count(); - var result = _processor.Apply(model, _posts); - var entry = result.FirstOrDefault(); - var resultCount = result.Count(); - - Assert.NotNull(entry); - Assert.Equal(1, resultCount); - Assert.Equal(3, entry.Id); + Assert.NotNull(entry); + Assert.Equal(1, resultCount); + Assert.Equal(3, entry.Id); + } } [Fact] public void OrValueFilteringWorks() { - var model = new SieveModel - { - Filters = "Title==C|D", - }; + var model = new SieveModel { Filters = "Title==C|D", }; - var result = _processor.Apply(model, _posts); - Assert.Equal(2, result.Count()); - Assert.True(result.Any(p => p.Id == 2)); - Assert.True(result.Any(p => p.Id == 3)); + foreach (var sieveProcessor in GetProcessors()) + { + var result = sieveProcessor.Apply(model, _posts); + Assert.Equal(2, result.Count()); + Assert.True(result.Any(p => p.Id == 2)); + Assert.True(result.Any(p => p.Id == 3)); + } } [Fact] public void OrValueFilteringWorks2() { - var model = new SieveModel - { - Filters = "Text@=(|)", - }; + var model = new SieveModel { Filters = "Text@=(|)", }; - var result = _processor.Apply(model, _comments); - Assert.Equal(1, result.Count()); - Assert.Equal(2, result.FirstOrDefault()?.Id); + foreach (var sieveProcessor in GetProcessors()) + { + var result = sieveProcessor.Apply(model, _comments); + Assert.Equal(1, result.Count()); + Assert.Equal(2, result.FirstOrDefault()?.Id); + } } [Fact] public void NestedFilteringWorks() { - var model = new SieveModel - { - Filters = "TopComment.Text!@=A", - }; + var model = new SieveModel { Filters = "TopComment.Text!@=A", }; - var result = _processor.Apply(model, _posts); - Assert.Equal(4, result.Count()); - var posts = result.ToList(); - Assert.Contains("B", posts[0].TopComment.Text); - Assert.Contains("C", posts[1].TopComment.Text); - Assert.Contains("D", posts[2].TopComment.Text); - Assert.Contains("Yen", posts[3].TopComment.Text); + foreach (var sieveProcessor in GetProcessors()) + { + var result = sieveProcessor.Apply(model, _posts); + Assert.Equal(_posts.Count(post => !post.TopComment.Text.Contains("A")), result.Count()); + var posts = result.ToList(); + Assert.Contains("B", posts[0].TopComment.Text); + Assert.Contains("C", posts[1].TopComment.Text); + Assert.Contains("D", posts[2].TopComment.Text); + Assert.Contains("E1", posts[3].TopComment.Text); + Assert.Contains("Yen", posts[4].TopComment.Text); + } } [Fact] public void NestedSortingWorks() { - var model = new SieveModel - { - Sorts = "TopComment.Id", - }; + var model = new SieveModel { Sorts = "TopComment.Id", }; - var result = _processor.Apply(model, _posts); - Assert.Equal(5, result.Count()); - var posts = result.ToList(); - Assert.Equal(0, posts[0].Id); - Assert.Equal(3, posts[1].Id); - Assert.Equal(2, posts[2].Id); - Assert.Equal(1, posts[3].Id); - Assert.Equal(4, posts[4].Id); + foreach (var sieveProcessor in GetProcessors()) + { + var result = sieveProcessor.Apply(model, _posts); + Assert.Equal(_posts.Count(), result.Count()); + var posts = result + .Select(post => post.Id) + .ToList(); + + Assert.True + ( + posts.SequenceEqual + ( + _posts + .AsEnumerable() + .OrderBy(post => post.TopComment.Id) + .Select(post => post.Id) + ) + ); + } } [Fact] @@ -554,16 +584,22 @@ namespace SieveUnitTests Filters = "(topc|featc)@=*2", }; - var result = _processor.Apply(model, _posts); - Assert.Equal(4, result.Count()); + foreach (var sieveProcessor in GetProcessors()) + { + var result = sieveProcessor.Apply(model, _posts); + Assert.Equal(4, result.Count()); + } model = new SieveModel { Filters = "(topc|featc)@=*B", }; - result = _processor.Apply(model, _posts); - Assert.Equal(1, result.Count()); + foreach (var sieveProcessor in GetProcessors()) + { + var result = sieveProcessor.Apply(model, _posts); + Assert.Equal(1, result.Count()); + } } [Fact] @@ -583,13 +619,13 @@ namespace SieveUnitTests }, }.AsQueryable(); - var model = new SieveModel - { - Filters = "FeaturedComment.Text!@=Some value", - }; + var model = new SieveModel { Filters = "FeaturedComment.Text!@=Some value", }; - var result = _processor.Apply(model, posts); - Assert.Equal(0, result.Count()); + foreach (var sieveProcessor in GetProcessors()) + { + var result = sieveProcessor.Apply(model, posts); + Assert.Equal(0, result.Count()); + } } [Fact] @@ -624,11 +660,14 @@ namespace SieveUnitTests Sorts = "TopComment.Id", }; - var result = _processor.Apply(model, posts); - Assert.Equal(2, result.Count()); - var sortedPosts = result.ToList(); - Assert.Equal(2, sortedPosts[0].Id); - Assert.Equal(1, sortedPosts[1].Id); + foreach (var sieveProcessor in GetProcessors()) + { + var result = sieveProcessor.Apply(model, posts); + Assert.Equal(2, result.Count()); + var sortedPosts = result.ToList(); + Assert.Equal(2, sortedPosts[0].Id); + Assert.Equal(1, sortedPosts[1].Id); + } } [Fact] @@ -663,10 +702,13 @@ namespace SieveUnitTests Filters = "FeaturedComment.Text==null", }; - var result = _processor.Apply(model, posts); - Assert.Equal(1, result.Count()); - var filteredPosts = result.ToList(); - Assert.Equal(2, filteredPosts[0].Id); + foreach (var sieveProcessor in GetProcessors()) + { + var result = sieveProcessor.Apply(model, posts); + Assert.Equal(1, result.Count()); + var filteredPosts = result.ToList(); + Assert.Equal(2, filteredPosts[0].Id); + } } [Fact] @@ -677,16 +719,25 @@ namespace SieveUnitTests Sorts = "-CreateDate" }; - var result = _processor.Apply(model, _posts); - Assert.Equal(5, result.Count()); + foreach (var sieveProcessor in GetProcessors()) + { + var result = sieveProcessor.Apply(model, _posts); + Assert.Equal(_posts.Count(), result.Count()); - var posts = result.ToList(); - Assert.Equal(4, posts[0].Id); - Assert.Equal(3,posts[1].Id); - Assert.Equal(2,posts[2].Id); - Assert.Equal(1,posts[3].Id); - Assert.Equal(0,posts[4].Id); + var posts = result + .Select(post => post.Id) + .ToList(); + Assert.True + ( + posts.SequenceEqual + ( + _posts + .OrderByDescending(post => post.DateCreated) + .Select(post => post.Id) + ) + ); + } } [Fact] @@ -713,8 +764,11 @@ namespace SieveUnitTests Filters = "Text==Here is\\, another comment" }; - var result = _processor.Apply(model, comments); - Assert.Equal(1, result.Count()); + foreach (var sieveProcessor in GetProcessors()) + { + var result = sieveProcessor.Apply(model, comments); + Assert.Equal(1, result.Count()); + } } [Fact] @@ -747,8 +801,11 @@ namespace SieveUnitTests Filters = @"Text==Here is \| a comment|Here is \| another comment|Here is \\\| another comment(1)", }; - var result = _processor.Apply(model, comments); - Assert.Equal(3, result.Count()); + foreach (var sieveProcessor in GetProcessors()) + { + var result = sieveProcessor.Apply(model, comments); + Assert.Equal(3, result.Count()); + } } [Theory] @@ -761,12 +818,15 @@ namespace SieveUnitTests Filters = filter }; - var result = _processor.Apply(model, _posts); - var entry = result.FirstOrDefault(); - var resultCount = result.Count(); + foreach (var sieveProcessor in GetProcessors()) + { + var result = sieveProcessor.Apply(model, _posts); + var entry = result.FirstOrDefault(); + var resultCount = result.Count(); - Assert.NotNull(entry); - Assert.Equal(1, resultCount); + Assert.NotNull(entry); + Assert.Equal(1, resultCount); + } } [Theory] @@ -792,12 +852,15 @@ namespace SieveUnitTests Filters = filter }; - var result = _processor.Apply(model, posts); - var entry = result.FirstOrDefault(); - var resultCount = result.Count(); + foreach (var sieveProcessor in GetProcessors()) + { + var result = sieveProcessor.Apply(model, posts); + var entry = result.FirstOrDefault(); + var resultCount = result.Count(); - Assert.NotNull(entry); - Assert.Equal(1, resultCount); + Assert.NotNull(entry); + Assert.Equal(1, resultCount); + } } [Theory] @@ -840,13 +903,15 @@ namespace SieveUnitTests Filters = filter, }; - var result = _processor.Apply(model, posts); - var entry = result.FirstOrDefault(); - var resultCount = result.Count(); + foreach (var sieveProcessor in GetProcessors()) + { + var result = sieveProcessor.Apply(model, posts); + var entry = result.FirstOrDefault(); + var resultCount = result.Count(); - Assert.NotNull(entry); - Assert.Equal(1, resultCount); + Assert.NotNull(entry); + Assert.Equal(1, resultCount); + } } - } } diff --git a/SieveUnitTests/GeneralWithInterfaces.cs b/SieveUnitTests/GeneralWithInterfaces.cs index f23db16..4adedcd 100644 --- a/SieveUnitTests/GeneralWithInterfaces.cs +++ b/SieveUnitTests/GeneralWithInterfaces.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using Sieve.Exceptions; using Sieve.Models; -using Sieve.Services; using SieveUnitTests.Abstractions.Entity; using SieveUnitTests.Entities; using SieveUnitTests.Services; @@ -12,28 +11,14 @@ using Xunit.Abstractions; namespace SieveUnitTests { - public class GeneralWithInterfaces + public class GeneralWithInterfaces: TestBase { - private readonly ITestOutputHelper _testOutputHelper; - private readonly SieveProcessor _processor; - private readonly SieveProcessor _nullableProcessor; private readonly IQueryable _posts; private readonly IQueryable _comments; public GeneralWithInterfaces(ITestOutputHelper testOutputHelper) + : base(testOutputHelper) { - var nullableAccessor = new SieveOptionsAccessor(); - nullableAccessor.Value.IgnoreNullsOnNotEqual = false; - - _testOutputHelper = testOutputHelper; - _processor = new ApplicationSieveProcessor(new SieveOptionsAccessor(), - new SieveCustomSortMethods(), - new SieveCustomFilterMethods()); - - _nullableProcessor = new ApplicationSieveProcessor(nullableAccessor, - new SieveCustomSortMethods(), - new SieveCustomFilterMethods()); - _posts = new List { new Post @@ -44,7 +29,8 @@ namespace SieveUnitTests IsDraft = true, CategoryId = null, TopComment = new Comment { Id = 0, Text = "A1" }, - FeaturedComment = new Comment { Id = 4, Text = "A2" } + FeaturedComment = new Comment { Id = 4, Text = "A2" }, + DeletedBy = "Me" }, new Post { @@ -54,7 +40,9 @@ namespace SieveUnitTests IsDraft = false, CategoryId = 1, TopComment = new Comment { Id = 3, Text = "B1" }, - FeaturedComment = new Comment { Id = 5, Text = "B2" } + FeaturedComment = new Comment { Id = 5, Text = "B2" }, + DeletedBy = "You", + UpdatedBy = "Me" }, new Post { @@ -63,7 +51,9 @@ namespace SieveUnitTests LikeCount = 0, CategoryId = 1, TopComment = new Comment { Id = 2, Text = "C1" }, - FeaturedComment = new Comment { Id = 6, Text = "C2" } + FeaturedComment = new Comment { Id = 6, Text = "C2" }, + DeletedBy = "Not Me", + UpdatedBy = "Your highness" }, new Post { @@ -73,8 +63,29 @@ namespace SieveUnitTests IsDraft = true, CategoryId = 2, TopComment = new Comment { Id = 1, Text = "D1" }, - FeaturedComment = new Comment { Id = 7, Text = "D2" } + FeaturedComment = new Comment { Id = 7, Text = "D2" }, + UpdatedBy = "Him" }, + new Post + { + Id = 4, + Title = "E", + LikeCount = 5, + IsDraft = false, + CategoryId = null, + TopComment = new Comment { Id = 4, Text = "E1" }, + UpdatedBy = "You" + }, + new Post + { + Id = 5, + Title = "Yen", + LikeCount = 5, + IsDraft = true, + CategoryId = 5, + TopComment = new Comment { Id = 4, Text = "Yen3" }, + FeaturedComment = new Comment { Id = 8, Text = "Yen4" } + } }.AsQueryable(); _comments = new List @@ -108,10 +119,13 @@ namespace SieveUnitTests Filters = "Title@=*a" }; - var result = _processor.Apply(model, _posts); + foreach (var sieveProcessor in GetProcessors()) + { + var result = sieveProcessor.Apply(model, _posts); - Assert.Equal(0, result.First().Id); - Assert.True(result.Count() == 1); + Assert.Equal(0, result.First().Id); + Assert.True(result.Count() == 1); + } } [Fact] @@ -122,23 +136,26 @@ namespace SieveUnitTests Filters = "Title!=*a" }; - var result = _processor.Apply(model, _posts); + foreach (var sieveProcessor in GetProcessors()) + { + var result = sieveProcessor.Apply(model, _posts); - Assert.Equal(1, result.First().Id); - Assert.True(result.Count() == 3); + Assert.Equal(1, result.First().Id); + Assert.True(result.Count() == _posts.Count(post => !post.Title.Contains("a", StringComparison.OrdinalIgnoreCase))); + } } [Fact] public void ContainsIsCaseSensitive() { - var model = new SieveModel + var model = new SieveModel { Filters = "Title@=a", }; + + foreach (var sieveProcessor in GetProcessors()) { - Filters = "Title@=a", - }; + var result = sieveProcessor.Apply(model, _posts); - var result = _processor.Apply(model, _posts); - - Assert.True(!result.Any()); + Assert.True(!result.Any()); + } } [Fact] @@ -149,153 +166,158 @@ namespace SieveUnitTests Filters = "Title!@=D", }; - var result = _processor.Apply(model, _posts); + foreach (var sieveProcessor in GetProcessors()) + { + var result = sieveProcessor.Apply(model, _posts); - Assert.True(result.Count() == 3); + Assert.True(result.Count() == _posts.Count(post => !post.Title.Contains("D"))); + } } [Fact] public void CanFilterBools() { - var model = new SieveModel + var model = new SieveModel { Filters = "IsDraft==false" }; + + foreach (var sieveProcessor in GetProcessors()) { - Filters = "IsDraft==false" - }; + var result = sieveProcessor.Apply(model, _posts); - var result = _processor.Apply(model, _posts); - - Assert.True(result.Count() == 2); + Assert.True(result.Count() == _posts.Count(post => !post.IsDraft)); + } } [Fact] public void CanSortBools() { - var model = new SieveModel + var model = new SieveModel { Sorts = "-IsDraft" }; + + foreach (var sieveProcessor in GetProcessors()) { - Sorts = "-IsDraft" - }; + var result = sieveProcessor.Apply(model, _posts); - var result = _processor.Apply(model, _posts); - - Assert.Equal(0, result.First().Id); + Assert.Equal(0, result.First().Id); + } } [Fact] public void CanFilterNullableInts() { - var model = new SieveModel + var model = new SieveModel { Filters = "CategoryId==1" }; + + foreach (var sieveProcessor in GetProcessors()) { - Filters = "CategoryId==1" - }; - - var result = _processor.Apply(model, _posts); - var nullableResult = _nullableProcessor.Apply(model, _posts); - - Assert.True(result.Count() == 2); - Assert.True(nullableResult.Count() == 2); + var result = sieveProcessor.Apply(model, _posts); + + Assert.True(result.Count() == _posts.Count(post => post.CategoryId == 1)); + } } [Fact] public void CanFilterNullableIntsWithNotEqual() { - var model = new SieveModel() + var model = new SieveModel() { Filters = "CategoryId!=1" }; + + foreach (var sieveProcessor in GetProcessors()) { - Filters = "CategoryId!=1" - }; + var result = sieveProcessor.Apply(model, _posts); - var result = _processor.Apply(model, _posts); - var nullableResult = _nullableProcessor.Apply(model, _posts); - - Assert.True(result.Count() == 1); - Assert.True(nullableResult.Count() == 2); + Assert.Equal + ( + (sieveProcessor as ApplicationSieveProcessor)?.ExposedOptions.IgnoreNullsOnNotEqual ?? true + ? _posts.Count(post => post.CategoryId != null && post.CategoryId != 1) + : _posts.Count(post => post.CategoryId != 1), + result.Count() + ); + } } [Fact] public void EqualsDoesntFailWithNonStringTypes() { - var model = new SieveModel + var model = new SieveModel { Filters = "LikeCount==50", }; + + TestOutputHelper.WriteLine(model.GetFiltersParsed()[0].Values.ToString()); + TestOutputHelper.WriteLine(model.GetFiltersParsed()[0].Operator); + TestOutputHelper.WriteLine(model.GetFiltersParsed()[0].OperatorParsed.ToString()); + + foreach (var sieveProcessor in GetProcessors()) { - Filters = "LikeCount==50", - }; + var result = sieveProcessor.Apply(model, _posts); - _testOutputHelper.WriteLine(model.GetFiltersParsed()[0].Values.ToString()); - _testOutputHelper.WriteLine(model.GetFiltersParsed()[0].Operator); - _testOutputHelper.WriteLine(model.GetFiltersParsed()[0].OperatorParsed.ToString()); - - var result = _processor.Apply(model, _posts); - - Assert.Equal(1, result.First().Id); - Assert.True(result.Count() == 1); + Assert.Equal(1, result.First().Id); + Assert.True(result.Count() == 1); + } } [Fact] public void CustomFiltersWork() { - var model = new SieveModel + var model = new SieveModel { Filters = "Isnew", }; + + foreach (var sieveProcessor in GetProcessors()) { - Filters = "Isnew", - }; + var result = sieveProcessor.Apply(model, _posts); - var result = _processor.Apply(model, _posts); - - Assert.False(result.Any(p => p.Id == 0)); - Assert.True(result.Count() == 3); + Assert.False(result.Any(p => p.Id == 0)); + Assert.True(result.Count() == _posts.Count(SieveCustomFilterMethods.IsNewFilterForIPost)); + } } [Fact] public void CustomGenericFiltersWork() { - var model = new SieveModel + var model = new SieveModel { Filters = "Latest", }; + + foreach (var sieveProcessor in GetProcessors()) { - Filters = "Latest", - }; + var result = sieveProcessor.Apply(model, _comments); - var result = _processor.Apply(model, _comments); - - Assert.False(result.Any(p => p.Id == 0)); - Assert.True(result.Count() == 2); + Assert.False(result.Any(p => p.Id == 0)); + Assert.True(result.Count() == 2); + } } [Fact] public void CustomFiltersWithOperatorsWork() { - var model = new SieveModel + var model = new SieveModel { Filters = "HasInTitle==A", }; + + foreach (var sieveProcessor in GetProcessors()) { - Filters = "HasInTitle==A", - }; + var result = sieveProcessor.Apply(model, _posts); - var result = _processor.Apply(model, _posts); - - Assert.True(result.Any(p => p.Id == 0)); - Assert.True(result.Count() == 1); + Assert.True(result.Any(p => p.Id == 0)); + Assert.True(result.Count() == 1); + } } [Fact] public void CustomFiltersMixedWithUsualWork1() { - var model = new SieveModel + var model = new SieveModel { Filters = "Isnew,CategoryId==2", }; + + foreach (var sieveProcessor in GetProcessors()) { - Filters = "Isnew,CategoryId==2", - }; + var result = sieveProcessor.Apply(model, _posts); - var result = _processor.Apply(model, _posts); - - Assert.True(result.Any(p => p.Id == 3)); - Assert.True(result.Count() == 1); + Assert.True(result.Any(p => p.Id == 3)); + Assert.True(result.Count() == 1); + } } [Fact] public void CustomFiltersMixedWithUsualWork2() { - var model = new SieveModel + var model = new SieveModel { Filters = "CategoryId==2,Isnew", }; + + foreach (var sieveProcessor in GetProcessors()) { - Filters = "CategoryId==2,Isnew", - }; + var result = sieveProcessor.Apply(model, _posts); - var result = _processor.Apply(model, _posts); - - Assert.True(result.Any(p => p.Id == 3)); - Assert.True(result.Count() == 1); + Assert.True(result.Any(p => p.Id == 3)); + Assert.True(result.Count() == 1); + } } [Fact] @@ -306,85 +328,91 @@ namespace SieveUnitTests Filters = "CategoryId==2,Isnew", }; - var postResult = _processor.Apply(postModel, _posts); + foreach (var sieveProcessor in GetProcessors()) + { + var postResult = sieveProcessor.Apply(postModel, _posts); - Assert.True(postResult.Any(p => p.Id == 3)); - Assert.Equal(1, postResult.Count()); + Assert.True(postResult.Any(p => p.Id == 3)); + Assert.Equal(1, postResult.Count()); + } var commentModel = new SieveModel { Filters = "Isnew", }; - var commentResult = _processor.Apply(commentModel, _comments); + foreach (var sieveProcessor in GetProcessors()) + { + var commentResult = sieveProcessor.Apply(commentModel, _comments); - Assert.True(commentResult.Any(c => c.Id == 2)); - Assert.Equal(2, commentResult.Count()); + Assert.True(commentResult.Any(c => c.Id == 2)); + Assert.Equal(2, commentResult.Count()); + } } [Fact] public void CustomSortsWork() { - var model = new SieveModel + var model = new SieveModel { Sorts = "Popularity", }; + + foreach (var sieveProcessor in GetProcessors()) { - Sorts = "Popularity", - }; + var result = sieveProcessor.Apply(model, _posts); - var result = _processor.Apply(model, _posts); - - Assert.False(result.First().Id == 0); + Assert.False(result.First().Id == 0); + } } [Fact] public void CustomGenericSortsWork() { - var model = new SieveModel + var model = new SieveModel { Sorts = "Oldest", }; + + foreach (var sieveProcessor in GetProcessors()) { - Sorts = "Oldest", - }; + var result = sieveProcessor.Apply(model, _posts); - var result = _processor.Apply(model, _posts); - - Assert.True(result.Last().Id == 0); + Assert.True(result.Last().Id == 0); + } } [Fact] public void MethodNotFoundExceptionWork() { - var model = new SieveModel - { - Filters = "does not exist", - }; + var model = new SieveModel { Filters = "does not exist", }; - Assert.Throws(() => _processor.Apply(model, _posts)); + foreach (var sieveProcessor in GetProcessors()) + { + Assert.Throws(() => sieveProcessor.Apply(model, _posts)); + } } [Fact] public void IncompatibleMethodExceptionsWork() { - var model = new SieveModel - { - Filters = "TestComment", - }; + var model = new SieveModel { Filters = "TestComment", }; - Assert.Throws(() => _processor.Apply(model, _posts)); + foreach (var sieveProcessor in GetProcessors()) + { + Assert.Throws(() => sieveProcessor.Apply(model, _posts)); + } } [Fact] public void OrNameFilteringWorks() { - var model = new SieveModel + var model = new SieveModel { Filters = "(Title|LikeCount)==3", }; + + foreach (var sieveProcessor in GetProcessors()) { - Filters = "(Title|LikeCount)==3", - }; + var result = sieveProcessor.Apply(model, _posts); + var entry = result.FirstOrDefault(); + var resultCount = result.Count(); - var result = _processor.Apply(model, _posts); - var entry = result.FirstOrDefault(); - var resultCount = result.Count(); - - Assert.NotNull(entry); - Assert.Equal(1, resultCount); - Assert.Equal(3, entry.Id); + Assert.NotNull(entry); + Assert.Equal(1, resultCount); + Assert.Equal(3, entry.Id); + } } [Theory] @@ -392,94 +420,105 @@ namespace SieveUnitTests [InlineData("(CategoryId|LikeCount)==50,CategoryId==1")] public void CombinedAndOrFilterIndependentOfOrder(string filter) { - var model = new SieveModel + var model = new SieveModel { Filters = filter, }; + + foreach (var sieveProcessor in GetProcessors()) { - Filters = filter, - }; + var result = sieveProcessor.Apply(model, _posts); + var entry = result.FirstOrDefault(); + var resultCount = result.Count(); - var result = _processor.Apply(model, _posts); - var entry = result.FirstOrDefault(); - var resultCount = result.Count(); - - Assert.NotNull(entry); - Assert.Equal(1, resultCount); + Assert.NotNull(entry); + Assert.Equal(1, resultCount); + } } [Fact] public void CombinedAndOrWithSpaceFilteringWorks() { - var model = new SieveModel + var model = new SieveModel { Filters = "Title==D, (Title|LikeCount)==3", }; + + foreach (var sieveProcessor in GetProcessors()) { - Filters = "Title==D, (Title|LikeCount)==3", - }; + var result = sieveProcessor.Apply(model, _posts); + var entry = result.FirstOrDefault(); + var resultCount = result.Count(); - var result = _processor.Apply(model, _posts); - var entry = result.FirstOrDefault(); - var resultCount = result.Count(); - - Assert.NotNull(entry); - Assert.Equal(1, resultCount); - Assert.Equal(3, entry.Id); + Assert.NotNull(entry); + Assert.Equal(1, resultCount); + Assert.Equal(3, entry.Id); + } } [Fact] public void OrValueFilteringWorks() { - var model = new SieveModel - { - Filters = "Title==C|D", - }; + var model = new SieveModel { Filters = "Title==C|D", }; - var result = _processor.Apply(model, _posts); - Assert.Equal(2, result.Count()); - Assert.True(result.Any(p => p.Id == 2)); - Assert.True(result.Any(p => p.Id == 3)); + foreach (var sieveProcessor in GetProcessors()) + { + var result = sieveProcessor.Apply(model, _posts); + Assert.Equal(2, result.Count()); + Assert.True(result.Any(p => p.Id == 2)); + Assert.True(result.Any(p => p.Id == 3)); + } } [Fact] public void OrValueFilteringWorks2() { - var model = new SieveModel - { - Filters = "Text@=(|)", - }; + var model = new SieveModel { Filters = "Text@=(|)", }; - var result = _processor.Apply(model, _comments); - Assert.Equal(1, result.Count()); - Assert.Equal(2, result.FirstOrDefault()?.Id); + foreach (var sieveProcessor in GetProcessors()) + { + var result = sieveProcessor.Apply(model, _comments); + Assert.Equal(1, result.Count()); + Assert.Equal(2, result.FirstOrDefault()?.Id); + } } [Fact] public void NestedFilteringWorks() { - var model = new SieveModel - { - Filters = "TopComment.Text!@=A", - }; + var model = new SieveModel { Filters = "TopComment.Text!@=A", }; - var result = _processor.Apply(model, _posts); - Assert.Equal(3, result.Count()); - var posts = result.ToList(); - Assert.Contains("B", posts[0].TopComment.Text); - Assert.Contains("C", posts[1].TopComment.Text); - Assert.Contains("D", posts[2].TopComment.Text); + foreach (var sieveProcessor in GetProcessors()) + { + var result = sieveProcessor.Apply(model, _posts); + Assert.Equal(_posts.Count(post => !post.TopComment.Text.Contains("A")), result.Count()); + var posts = result.ToList(); + Assert.Contains("B", posts[0].TopComment.Text); + Assert.Contains("C", posts[1].TopComment.Text); + Assert.Contains("D", posts[2].TopComment.Text); + Assert.Contains("E1", posts[3].TopComment.Text); + Assert.Contains("Yen", posts[4].TopComment.Text); + } } [Fact] public void NestedSortingWorks() { - var model = new SieveModel - { - Sorts = "TopComment.Id", - }; + var model = new SieveModel { Sorts = "TopComment.Id", }; - var result = _processor.Apply(model, _posts); - Assert.Equal(4, result.Count()); - var posts = result.ToList(); - Assert.Equal(0, posts[0].Id); - Assert.Equal(3, posts[1].Id); - Assert.Equal(2, posts[2].Id); - Assert.Equal(1, posts[3].Id); + foreach (var sieveProcessor in GetProcessors()) + { + var result = sieveProcessor.Apply(model, _posts); + Assert.Equal(_posts.Count(), result.Count()); + var posts = result + .Select(post => post.Id) + .ToList(); + + Assert.True + ( + posts.SequenceEqual + ( + _posts + .AsEnumerable() + .OrderBy(post => post.TopComment.Id) + .Select(post => post.Id) + ) + ); + } } [Fact] @@ -490,16 +529,22 @@ namespace SieveUnitTests Filters = "(topc|featc)@=*2", }; - var result = _processor.Apply(model, _posts); - Assert.Equal(4, result.Count()); - + foreach (var sieveProcessor in GetProcessors()) + { + var result = sieveProcessor.Apply(model, _posts); + Assert.Equal(4, result.Count()); + } + model = new SieveModel { Filters = "(topc|featc)@=*B", }; - result = _processor.Apply(model, _posts); - Assert.Equal(1, result.Count()); + foreach (var sieveProcessor in GetProcessors()) + { + var result = sieveProcessor.Apply(model, _posts); + Assert.Equal(1, result.Count()); + } } [Fact] @@ -519,13 +564,13 @@ namespace SieveUnitTests }, }.AsQueryable(); - var model = new SieveModel - { - Filters = "FeaturedComment.Text!@=Some value", - }; + var model = new SieveModel { Filters = "FeaturedComment.Text!@=Some value", }; - var result = _processor.Apply(model, posts); - Assert.Equal(0, result.Count()); + foreach (var sieveProcessor in GetProcessors()) + { + var result = sieveProcessor.Apply(model, posts); + Assert.Equal(0, result.Count()); + } } [Fact] @@ -540,7 +585,7 @@ namespace SieveUnitTests LikeCount = 0, IsDraft = false, CategoryId = null, - TopComment = new Comment { Id = 1 }, + TopComment = new Comment { Id = 1 }, FeaturedComment = null }, new Post @@ -555,16 +600,16 @@ namespace SieveUnitTests }, }.AsQueryable(); - var model = new SieveModel - { - Sorts = "TopComment.Id", - }; + var model = new SieveModel { Sorts = "TopComment.Id", }; - var result = _processor.Apply(model, posts); - Assert.Equal(2, result.Count()); - var sortedPosts = result.ToList(); - Assert.Equal(2, sortedPosts[0].Id); - Assert.Equal(1, sortedPosts[1].Id); + foreach (var sieveProcessor in GetProcessors()) + { + var result = sieveProcessor.Apply(model, posts); + Assert.Equal(2, result.Count()); + var sortedPosts = result.ToList(); + Assert.Equal(2, sortedPosts[0].Id); + Assert.Equal(1, sortedPosts[1].Id); + } } [Fact] @@ -590,37 +635,98 @@ namespace SieveUnitTests IsDraft = false, CategoryId = null, TopComment = null, - FeaturedComment = new Comment { Id = 1, Text = null } + FeaturedComment = new Comment { Id = 1, Text = null } }, }.AsQueryable(); - var model = new SieveModel - { - Filters = "FeaturedComment.Text==null", - }; + var model = new SieveModel { Filters = "FeaturedComment.Text==null", }; - var result = _processor.Apply(model, posts); - Assert.Equal(1, result.Count()); - var filteredPosts = result.ToList(); - Assert.Equal(2, filteredPosts[0].Id); + foreach (var sieveProcessor in GetProcessors()) + { + var result = sieveProcessor.Apply(model, posts); + Assert.Equal(1, result.Count()); + var filteredPosts = result.ToList(); + Assert.Equal(2, filteredPosts[0].Id); + } } [Fact] public void BaseDefinedPropertyMappingSortingWorks_WithCustomName() { - var model = new SieveModel + var model = new SieveModel { Sorts = "-CreateDate" }; + + foreach (var sieveProcessor in GetProcessors()) { - Sorts = "-CreateDate" - }; + var result = sieveProcessor.Apply(model, _posts); + Assert.Equal(_posts.Count(), result.Count()); - var result = _processor.Apply(model, _posts); - Assert.Equal(4, result.Count()); + var posts = result + .Select(post => post.Id) + .ToList(); - var posts = result.ToList(); - Assert.Equal(3,posts[0].Id); - Assert.Equal(2,posts[1].Id); - Assert.Equal(1,posts[2].Id); - Assert.Equal(0,posts[3].Id); + Assert.True + ( + posts.SequenceEqual + ( + _posts + .OrderByDescending(post => post.DateCreated) + .Select(post => post.Id) + ) + ); + } + } + + [Theory] + [InlineData("DeletedBy")] + [InlineData("-DeletedBy")] + public void CanSortWithInterfaceProperties(string sort) + { + var model = new SieveModel { Sorts = sort }; + + foreach (var sieveProcessor in GetProcessors()) + { + + var result = sieveProcessor.Apply(model, _posts); + var entry = result.FirstOrDefault(); + var resultCount = result.Count(); + + Assert.NotNull(entry); + Assert.Equal(_posts.Count(), resultCount); + } + } + + [Theory] + [InlineData("UpdatedBy==Me", 1, 1, new [] {1})] + [InlineData("UpdatedBy==Me|You", 2, 2, null)] + [InlineData("UpdatedBy==You|Me", 2, 2, null)] + [InlineData("UpdatedBy!=Him", 5, 3, null)] + [InlineData("UpdatedBy_=*You", 2, 2, null)] + public void CanFilterWithInterfaceProperties(string filters, int recordCount, int nullIgnoredRecordCount, int[] filteredIds) + { + var model = new SieveModel { Filters = filters, Sorts = "Id" }; + + foreach (var sieveProcessor in GetProcessors()) + { + var result = sieveProcessor.Apply(model, _posts); + var resultCount = result.Count(); + Assert.Equal + ( + (sieveProcessor as ApplicationSieveProcessor)?.ExposedOptions.IgnoreNullsOnNotEqual ?? true + ? nullIgnoredRecordCount + : recordCount, + resultCount + ); + + if (filteredIds != null) + { + Assert.True + ( + result + .Select(post => post.Id) + .SequenceEqual(filteredIds) + ); + } + } } } } diff --git a/SieveUnitTests/Services/ApplicationSieveProcessor.cs b/SieveUnitTests/Services/ApplicationSieveProcessor.cs index f0a8815..1cf96c6 100644 --- a/SieveUnitTests/Services/ApplicationSieveProcessor.cs +++ b/SieveUnitTests/Services/ApplicationSieveProcessor.cs @@ -16,62 +16,20 @@ namespace SieveUnitTests.Services { } + public SieveOptions ExposedOptions => new SieveOptions() + { + IgnoreNullsOnNotEqual = Options.Value.IgnoreNullsOnNotEqual, + CaseSensitive = Options.Value.CaseSensitive, + DefaultPageSize = Options.Value.DefaultPageSize, + DisableNullableTypeExpressionForSorting = Options.Value.DisableNullableTypeExpressionForSorting, + MaxPageSize = Options.Value.MaxPageSize, + ThrowExceptions = Options.Value.ThrowExceptions, + }; + protected override SievePropertyMapper MapProperties(SievePropertyMapper mapper) { - mapper.Property(p => p.ThisHasNoAttributeButIsAccessible) - .CanSort() - .CanFilter() - .HasName("shortname"); - - mapper.Property(p => p.TopComment.Text) - .CanFilter(); - - mapper.Property(p => p.TopComment.Id) - .CanSort(); - - mapper.Property(p => p.OnlySortableViaFluentApi) - .CanSort(); - - mapper.Property(p => p.TopComment.Text) - .CanFilter() - .HasName("topc"); - - mapper.Property(p => p.FeaturedComment.Text) - .CanFilter() - .HasName("featc"); - - mapper - .Property(p => p.DateCreated) - .CanSort() - .HasName("CreateDate"); - - // interfaces - mapper.Property(p => p.ThisHasNoAttributeButIsAccessible) - .CanSort() - .CanFilter() - .HasName("shortname"); - - mapper.Property(p => p.TopComment.Text) - .CanFilter(); - - mapper.Property(p => p.TopComment.Id) - .CanSort(); - - mapper.Property(p => p.OnlySortableViaFluentApi) - .CanSort(); - - mapper.Property(p => p.TopComment.Text) - .CanFilter() - .HasName("topc"); - - mapper.Property(p => p.FeaturedComment.Text) - .CanFilter() - .HasName("featc"); - - mapper - .Property(p => p.DateCreated) - .CanSort() - .HasName("CreateDate"); + SieveConfigurationForPost.ConfigureStatic(mapper); + SieveConfigurationForIPost.ConfigureStatic(mapper); return mapper; } diff --git a/SieveUnitTests/Services/SieveCustomFilterMethods.cs b/SieveUnitTests/Services/SieveCustomFilterMethods.cs index 0f534e3..d156a26 100644 --- a/SieveUnitTests/Services/SieveCustomFilterMethods.cs +++ b/SieveUnitTests/Services/SieveCustomFilterMethods.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Linq.Expressions; using Sieve.Services; using SieveUnitTests.Abstractions.Entity; using SieveUnitTests.Entities; @@ -8,9 +9,14 @@ namespace SieveUnitTests.Services { public class SieveCustomFilterMethods : ISieveCustomFilterMethods { + internal static readonly Expression> IsNewFilterForPost = post => post.LikeCount < 100; + internal static readonly Expression> IsNewFilterForIPost = post => post.LikeCount < 100; + internal static readonly Expression> IsNewFilterForComment = comment => comment.DateCreated > DateTimeOffset.UtcNow.AddDays(-2); + internal static readonly Expression> IsNewFilterForIComment = comment => comment.DateCreated > DateTimeOffset.UtcNow.AddDays(-2); + public IQueryable IsNew(IQueryable source, string op, string[] values) { - var result = source.Where(p => p.LikeCount < 100); + var result = source.Where(IsNewFilterForPost); return result; } @@ -24,7 +30,7 @@ namespace SieveUnitTests.Services public IQueryable IsNew(IQueryable source, string op, string[] values) { - var result = source.Where(c => c.DateCreated > DateTimeOffset.UtcNow.AddDays(-2)); + var result = source.Where(IsNewFilterForComment); return result; } @@ -42,7 +48,7 @@ namespace SieveUnitTests.Services public IQueryable IsNew(IQueryable source, string op, string[] values) { - var result = source.Where(p => p.LikeCount < 100); + var result = source.Where(IsNewFilterForIPost); return result; } @@ -56,7 +62,7 @@ namespace SieveUnitTests.Services public IQueryable IsNew(IQueryable source, string op, string[] values) { - var result = source.Where(c => c.DateCreated > DateTimeOffset.UtcNow.AddDays(-2)); + var result = source.Where(IsNewFilterForIComment); return result; } diff --git a/SieveUnitTests/SieveUnitTests.csproj b/SieveUnitTests/SieveUnitTests.csproj index 1c26e54..017a5a2 100644 --- a/SieveUnitTests/SieveUnitTests.csproj +++ b/SieveUnitTests/SieveUnitTests.csproj @@ -1,19 +1,20 @@ - netcoreapp3.1 + net6.0 false - - - - + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/SieveUnitTests/TestBase.cs b/SieveUnitTests/TestBase.cs new file mode 100644 index 0000000..24f319c --- /dev/null +++ b/SieveUnitTests/TestBase.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using Sieve.Services; +using SieveUnitTests.Services; +using Xunit.Abstractions; + +namespace SieveUnitTests +{ + public abstract class TestBase + { + protected TestBase(ITestOutputHelper testOutputHelper) + { + TestOutputHelper = testOutputHelper; + } + + protected ITestOutputHelper TestOutputHelper { get; } + + /// + /// Processors with the same mappings but configured via a different method. + /// + /// + public static IEnumerable GetProcessors() + { + // normal processor + yield return new ApplicationSieveProcessor( + new SieveOptionsAccessor(), + new SieveCustomSortMethods(), + new SieveCustomFilterMethods()); + + // nullable processor + yield return new ApplicationSieveProcessor( + new SieveOptionsAccessor() { Value = { IgnoreNullsOnNotEqual = false } }, + new SieveCustomSortMethods(), + new SieveCustomFilterMethods()); + + // modular processor + yield return new ModularConfigurationSieveProcessor( + new SieveOptionsAccessor(), + new SieveCustomSortMethods(), + new SieveCustomFilterMethods()); + + // modular processor with scan + yield return new ModularConfigurationWithScanSieveProcessor( + new SieveOptionsAccessor(), + new SieveCustomSortMethods(), + new SieveCustomFilterMethods()); + } + } +} diff --git a/build/Build.cs b/build/Build.cs index 3146ffc..4fbd27e 100644 --- a/build/Build.cs +++ b/build/Build.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using GlobExpressions; using Nuke.Common; using Nuke.Common.CI; @@ -13,7 +13,6 @@ using Nuke.Common.Utilities.Collections; using static Nuke.Common.IO.FileSystemTasks; using static Nuke.Common.Tools.DotNet.DotNetTasks; -[CheckBuildProjectConfigurations] [ShutdownDotNetAfterServerBuild] [GitHubActions("ci", GitHubActionsImage.UbuntuLatest, OnPullRequestBranches = new[] {"master", "releases/*"}, @@ -34,7 +33,7 @@ class Build : NukeBuild [GitRepository] readonly GitRepository GitRepository; - [GitVersion(Framework = "netcoreapp3.1")] readonly GitVersion GitVersion; + [GitVersion] readonly GitVersion GitVersion; [Solution] readonly Solution Solution; @@ -102,8 +101,14 @@ class Build : NukeBuild .Requires(() => Configuration.Equals(Configuration.Release)) .Executes(() => { - Glob.Files(OutputDirectory, "*.nupkg") - .NotEmpty() + var files = Glob + .Files(OutputDirectory, "*.nupkg") + .NotNull() + .ToList(); + + Assert.NotEmpty(files); + + files .ForEach(x => { DotNetNuGetPush(s => s diff --git a/build/Configuration.cs b/build/Configuration.cs index 9c08b1a..f66c1ad 100644 --- a/build/Configuration.cs +++ b/build/Configuration.cs @@ -1,6 +1,4 @@ -using System; -using System.ComponentModel; -using System.Linq; +using System.ComponentModel; using Nuke.Common.Tooling; [TypeConverter(typeof(TypeConverter))]