mirror of
https://github.com/Biarity/Sieve.git
synced 2024-11-21 13:02:46 +01:00
* Migrate tests to xunit
* Update sample project to dotnetcore3.1 * Use Sqlite in sample project to run it everywhere * Fix: Filter with escaped comma * Fix: Filter "null" does not work with Contains or StartsWith * Code cleanup: Adjust namespaces, adjust usings
This commit is contained in:
parent
18eedf2e1a
commit
428acd7558
8
.gitignore
vendored
8
.gitignore
vendored
@ -260,4 +260,10 @@ paket-files/
|
|||||||
|
|
||||||
# Python Tools for Visual Studio (PTVS)
|
# Python Tools for Visual Studio (PTVS)
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.pyc
|
*.pyc
|
||||||
|
|
||||||
|
# Nuke output
|
||||||
|
/output
|
||||||
|
|
||||||
|
# Sample database
|
||||||
|
Sieve.Sample/Sieve.db
|
||||||
|
@ -2,10 +2,10 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Sieve.Models;
|
using Sieve.Models;
|
||||||
|
using Sieve.Sample.Entities;
|
||||||
using Sieve.Services;
|
using Sieve.Services;
|
||||||
using SieveTests.Entities;
|
|
||||||
|
|
||||||
namespace SieveTests.Controllers
|
namespace Sieve.Sample.Controllers
|
||||||
{
|
{
|
||||||
[Route("api/[controller]/[action]")]
|
[Route("api/[controller]/[action]")]
|
||||||
public class PostsController : Controller
|
public class PostsController : Controller
|
@ -1,6 +1,6 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace SieveTests.Entities
|
namespace Sieve.Sample.Entities
|
||||||
{
|
{
|
||||||
public class ApplicationDbContext : DbContext
|
public class ApplicationDbContext : DbContext
|
||||||
{
|
{
|
@ -2,14 +2,14 @@
|
|||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using Sieve.Attributes;
|
using Sieve.Attributes;
|
||||||
|
|
||||||
namespace SieveTests.Entities
|
namespace Sieve.Sample.Entities
|
||||||
{
|
{
|
||||||
public class Post
|
public class Post
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
|
||||||
[Sieve(CanFilter = true, CanSort = true)]
|
[Sieve(CanFilter = true, CanSort = true)]
|
||||||
public string Title { get; set; } = Guid.NewGuid().ToString().Replace("-", string.Empty).Substring(0, 8);
|
public string Title { get; set; } = Guid.NewGuid().ToString().Replace("-", string.Empty)[..8];
|
||||||
|
|
||||||
[Sieve(CanFilter = true, CanSort = true)]
|
[Sieve(CanFilter = true, CanSort = true)]
|
||||||
public int LikeCount { get; set; } = new Random().Next(0, 1000);
|
public int LikeCount { get; set; } = new Random().Next(0, 1000);
|
52
Sieve.Sample/Migrations/20210513114647_Initial.Designer.cs
generated
Normal file
52
Sieve.Sample/Migrations/20210513114647_Initial.Designer.cs
generated
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using Sieve.Sample.Entities;
|
||||||
|
|
||||||
|
namespace Sieve.Sample.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(ApplicationDbContext))]
|
||||||
|
[Migration("20210513114647_Initial")]
|
||||||
|
partial class Initial
|
||||||
|
{
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "3.1.14");
|
||||||
|
|
||||||
|
modelBuilder.Entity("Sieve.Sample.Entities.Post", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int?>("CategoryId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("CommentCount")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("DateCreated")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("DateLastViewed")
|
||||||
|
.HasColumnType("datetime");
|
||||||
|
|
||||||
|
b.Property<int>("LikeCount")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Title")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Posts");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using Microsoft.EntityFrameworkCore.Metadata;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
namespace SieveTests.Migrations
|
namespace Sieve.Sample.Migrations
|
||||||
{
|
{
|
||||||
public partial class Init : Migration
|
public partial class Initial : Migration
|
||||||
{
|
{
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
{
|
{
|
||||||
@ -13,14 +12,18 @@ namespace SieveTests.Migrations
|
|||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
Id = table.Column<int>(nullable: false)
|
Id = table.Column<int>(nullable: false)
|
||||||
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
Title = table.Column<string>(nullable: true),
|
||||||
|
LikeCount = table.Column<int>(nullable: false),
|
||||||
CommentCount = table.Column<int>(nullable: false),
|
CommentCount = table.Column<int>(nullable: false),
|
||||||
DateCreated = table.Column<DateTimeOffset>(nullable: false),
|
DateCreated = table.Column<DateTimeOffset>(nullable: false),
|
||||||
LikeCount = table.Column<int>(nullable: false),
|
DateLastViewed = table.Column<DateTime>(type: "datetime", nullable: false),
|
||||||
Title = table.Column<string>(nullable: true),
|
|
||||||
CategoryId = table.Column<int>(nullable: true)
|
CategoryId = table.Column<int>(nullable: true)
|
||||||
},
|
},
|
||||||
constraints: table => table.PrimaryKey("PK_Posts", x => x.Id));
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Posts", x => x.Id);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
protected override void Down(MigrationBuilder migrationBuilder)
|
50
Sieve.Sample/Migrations/ApplicationDbContextModelSnapshot.cs
Normal file
50
Sieve.Sample/Migrations/ApplicationDbContextModelSnapshot.cs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using Sieve.Sample.Entities;
|
||||||
|
|
||||||
|
namespace Sieve.Sample.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(ApplicationDbContext))]
|
||||||
|
partial class ApplicationDbContextModelSnapshot : ModelSnapshot
|
||||||
|
{
|
||||||
|
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "3.1.14");
|
||||||
|
|
||||||
|
modelBuilder.Entity("Sieve.Sample.Entities.Post", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int?>("CategoryId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("CommentCount")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("DateCreated")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("DateLastViewed")
|
||||||
|
.HasColumnType("datetime");
|
||||||
|
|
||||||
|
b.Property<int>("LikeCount")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Title")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Posts");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
using Microsoft.AspNetCore;
|
using Microsoft.AspNetCore;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
|
||||||
namespace SieveTests
|
namespace Sieve.Sample
|
||||||
{
|
{
|
||||||
public static class Program
|
public static class Program
|
||||||
{
|
{
|
@ -1,9 +1,9 @@
|
|||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Sieve.Models;
|
using Sieve.Models;
|
||||||
|
using Sieve.Sample.Entities;
|
||||||
using Sieve.Services;
|
using Sieve.Services;
|
||||||
using SieveTests.Entities;
|
|
||||||
|
|
||||||
namespace SieveTests.Services
|
namespace Sieve.Sample.Services
|
||||||
{
|
{
|
||||||
public class ApplicationSieveProcessor : SieveProcessor
|
public class ApplicationSieveProcessor : SieveProcessor
|
||||||
{
|
{
|
@ -1,8 +1,8 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Sieve.Sample.Entities;
|
||||||
using Sieve.Services;
|
using Sieve.Services;
|
||||||
using SieveTests.Entities;
|
|
||||||
|
|
||||||
namespace SieveTests.Services
|
namespace Sieve.Sample.Services
|
||||||
{
|
{
|
||||||
public class SieveCustomFilterMethods : ISieveCustomFilterMethods
|
public class SieveCustomFilterMethods : ISieveCustomFilterMethods
|
||||||
{
|
{
|
@ -1,8 +1,8 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Sieve.Sample.Entities;
|
||||||
using Sieve.Services;
|
using Sieve.Services;
|
||||||
using SieveTests.Entities;
|
|
||||||
|
|
||||||
namespace SieveTests.Services
|
namespace Sieve.Sample.Services
|
||||||
{
|
{
|
||||||
public class SieveCustomSortMethods : ISieveCustomSortMethods
|
public class SieveCustomSortMethods : ISieveCustomSortMethods
|
||||||
{
|
{
|
20
Sieve.Sample/Sieve.Sample.csproj
Normal file
20
Sieve.Sample/Sieve.Sample.csproj
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.14">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.14" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.14" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\SieveUnitTests\SieveUnitTests.csproj" />
|
||||||
|
<ProjectReference Include="..\Sieve\Sieve.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
60
Sieve.Sample/Startup.cs
Normal file
60
Sieve.Sample/Startup.cs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Sieve.Models;
|
||||||
|
using Sieve.Sample.Entities;
|
||||||
|
using Sieve.Sample.Services;
|
||||||
|
using Sieve.Services;
|
||||||
|
|
||||||
|
namespace Sieve.Sample
|
||||||
|
{
|
||||||
|
public class Startup
|
||||||
|
{
|
||||||
|
public Startup(IConfiguration configuration)
|
||||||
|
{
|
||||||
|
Configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IConfiguration Configuration { get; }
|
||||||
|
|
||||||
|
public void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddMvc(opts =>
|
||||||
|
{
|
||||||
|
opts.EnableEndpointRouting = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
services.AddDbContext<ApplicationDbContext>(options =>
|
||||||
|
options.UseSqlite("Data Source=.\\sieve.db"));
|
||||||
|
|
||||||
|
services.Configure<SieveOptions>(Configuration.GetSection("Sieve"));
|
||||||
|
|
||||||
|
services.AddScoped<ISieveCustomSortMethods, SieveCustomSortMethods>();
|
||||||
|
services.AddScoped<ISieveCustomFilterMethods, SieveCustomFilterMethods>();
|
||||||
|
services.AddScoped<ISieveProcessor, ApplicationSieveProcessor>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||||
|
{
|
||||||
|
PrepareDatabase(app);
|
||||||
|
|
||||||
|
if (env.IsDevelopment())
|
||||||
|
{
|
||||||
|
app.UseDeveloperExceptionPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
app.UseMvc();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void PrepareDatabase(IApplicationBuilder app)
|
||||||
|
{
|
||||||
|
using var scope = app.ApplicationServices.CreateScope();
|
||||||
|
var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||||
|
dbContext.Database.EnsureDeleted();
|
||||||
|
dbContext.Database.Migrate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,7 +5,7 @@ VisualStudioVersion = 15.0.27130.2027
|
|||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sieve", "Sieve\Sieve.csproj", "{B32B8B33-94B0-40E3-8FE5-D54602222717}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sieve", "Sieve\Sieve.csproj", "{B32B8B33-94B0-40E3-8FE5-D54602222717}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SieveTests", "SieveTests\SieveTests.csproj", "{8043D264-42A0-4275-97A1-46400C02E37E}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sieve.Sample", "Sieve.Sample\Sieve.Sample.csproj", "{8043D264-42A0-4275-97A1-46400C02E37E}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SieveUnitTests", "SieveUnitTests\SieveUnitTests.csproj", "{21C3082D-F40E-457F-BE2E-AA099E19E199}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SieveUnitTests", "SieveUnitTests\SieveUnitTests.csproj", "{21C3082D-F40E-457F-BE2E-AA099E19E199}"
|
||||||
EndProject
|
EndProject
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
@ -13,7 +14,15 @@ namespace Sieve.Models
|
|||||||
where TFilterTerm : IFilterTerm, new()
|
where TFilterTerm : IFilterTerm, new()
|
||||||
where TSortTerm : ISortTerm, new()
|
where TSortTerm : ISortTerm, new()
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Pattern used to split filters and sorts by comma.
|
||||||
|
/// </summary>
|
||||||
private const string EscapedCommaPattern = @"(?<!($|[^\\])(\\\\)*?\\),\s*";
|
private const string EscapedCommaPattern = @"(?<!($|[^\\])(\\\\)*?\\),\s*";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Escaped comma e.g. used in filter filter string.
|
||||||
|
/// </summary>
|
||||||
|
private const string EscapedComma = @"\,";
|
||||||
|
|
||||||
[DataMember]
|
[DataMember]
|
||||||
public string Filters { get; set; }
|
public string Filters { get; set; }
|
||||||
@ -34,15 +43,20 @@ namespace Sieve.Models
|
|||||||
var value = new List<TFilterTerm>();
|
var value = new List<TFilterTerm>();
|
||||||
foreach (var filter in Regex.Split(Filters, EscapedCommaPattern))
|
foreach (var filter in Regex.Split(Filters, EscapedCommaPattern))
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(filter)) continue;
|
if (string.IsNullOrWhiteSpace(filter))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var filterValue = filter.Replace(EscapedComma, ",");
|
||||||
|
|
||||||
if (filter.StartsWith("("))
|
if (filter.StartsWith("("))
|
||||||
{
|
{
|
||||||
var filterOpAndVal = filter.Substring(filter.LastIndexOf(")") + 1);
|
var filterOpAndVal = filterValue[(filterValue.LastIndexOf(")", StringComparison.Ordinal) + 1)..];
|
||||||
var subfilters = filter.Replace(filterOpAndVal, "").Replace("(", "").Replace(")", "");
|
var subFilters = filterValue.Replace(filterOpAndVal, "").Replace("(", "").Replace(")", "");
|
||||||
var filterTerm = new TFilterTerm
|
var filterTerm = new TFilterTerm
|
||||||
{
|
{
|
||||||
Filter = subfilters + filterOpAndVal
|
Filter = subFilters + filterOpAndVal
|
||||||
};
|
};
|
||||||
value.Add(filterTerm);
|
value.Add(filterTerm);
|
||||||
}
|
}
|
||||||
@ -50,7 +64,7 @@ namespace Sieve.Models
|
|||||||
{
|
{
|
||||||
var filterTerm = new TFilterTerm
|
var filterTerm = new TFilterTerm
|
||||||
{
|
{
|
||||||
Filter = filter
|
Filter = filterValue
|
||||||
};
|
};
|
||||||
value.Add(filterTerm);
|
value.Add(filterTerm);
|
||||||
}
|
}
|
||||||
@ -65,29 +79,28 @@ namespace Sieve.Models
|
|||||||
|
|
||||||
public List<TSortTerm> GetSortsParsed()
|
public List<TSortTerm> GetSortsParsed()
|
||||||
{
|
{
|
||||||
if (Sorts != null)
|
if (Sorts == null)
|
||||||
{
|
|
||||||
var value = new List<TSortTerm>();
|
|
||||||
foreach (var sort in Regex.Split(Sorts, EscapedCommaPattern))
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(sort)) continue;
|
|
||||||
|
|
||||||
var sortTerm = new TSortTerm()
|
|
||||||
{
|
|
||||||
Sort = sort
|
|
||||||
};
|
|
||||||
if (!value.Any(s => s.Name == sortTerm.Name))
|
|
||||||
{
|
|
||||||
value.Add(sortTerm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var value = new List<TSortTerm>();
|
||||||
|
foreach (var sort in Regex.Split(Sorts, EscapedCommaPattern))
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(sort)) continue;
|
||||||
|
|
||||||
|
var sortTerm = new TSortTerm
|
||||||
|
{
|
||||||
|
Sort = sort
|
||||||
|
};
|
||||||
|
|
||||||
|
if (value.All(s => s.Name != sortTerm.Name))
|
||||||
|
{
|
||||||
|
value.Add(sortTerm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,40 +14,50 @@ namespace Sieve.Services
|
|||||||
{
|
{
|
||||||
public class SieveProcessor : SieveProcessor<SieveModel, FilterTerm, SortTerm>, ISieveProcessor
|
public class SieveProcessor : SieveProcessor<SieveModel, FilterTerm, SortTerm>, ISieveProcessor
|
||||||
{
|
{
|
||||||
public SieveProcessor(IOptions<SieveOptions> options) : base(options)
|
public SieveProcessor(IOptions<SieveOptions> options)
|
||||||
|
: base(options)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public SieveProcessor(IOptions<SieveOptions> options, ISieveCustomSortMethods customSortMethods) : base(options, customSortMethods)
|
public SieveProcessor(IOptions<SieveOptions> options, ISieveCustomSortMethods customSortMethods)
|
||||||
|
: base(options, customSortMethods)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public SieveProcessor(IOptions<SieveOptions> options, ISieveCustomFilterMethods customFilterMethods) : base(options, customFilterMethods)
|
public SieveProcessor(IOptions<SieveOptions> options, ISieveCustomFilterMethods customFilterMethods)
|
||||||
|
: base(options, customFilterMethods)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public SieveProcessor(IOptions<SieveOptions> options, ISieveCustomSortMethods customSortMethods, ISieveCustomFilterMethods customFilterMethods) : base(options, customSortMethods, customFilterMethods)
|
public SieveProcessor(IOptions<SieveOptions> options, ISieveCustomSortMethods customSortMethods,
|
||||||
|
ISieveCustomFilterMethods customFilterMethods) : base(options, customSortMethods, customFilterMethods)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SieveProcessor<TFilterTerm, TSortTerm> : SieveProcessor<SieveModel<TFilterTerm, TSortTerm>, TFilterTerm, TSortTerm>, ISieveProcessor<TFilterTerm, TSortTerm>
|
public class SieveProcessor<TFilterTerm, TSortTerm> :
|
||||||
|
SieveProcessor<SieveModel<TFilterTerm, TSortTerm>, TFilterTerm, TSortTerm>, ISieveProcessor<TFilterTerm, TSortTerm>
|
||||||
where TFilterTerm : IFilterTerm, new()
|
where TFilterTerm : IFilterTerm, new()
|
||||||
where TSortTerm : ISortTerm, new()
|
where TSortTerm : ISortTerm, new()
|
||||||
{
|
{
|
||||||
public SieveProcessor(IOptions<SieveOptions> options) : base(options)
|
public SieveProcessor(IOptions<SieveOptions> options)
|
||||||
|
: base(options)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public SieveProcessor(IOptions<SieveOptions> options, ISieveCustomSortMethods customSortMethods) : base(options, customSortMethods)
|
public SieveProcessor(IOptions<SieveOptions> options, ISieveCustomSortMethods customSortMethods)
|
||||||
|
: base(options, customSortMethods)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public SieveProcessor(IOptions<SieveOptions> options, ISieveCustomFilterMethods customFilterMethods) : base(options, customFilterMethods)
|
public SieveProcessor(IOptions<SieveOptions> options, ISieveCustomFilterMethods customFilterMethods)
|
||||||
|
: base(options, customFilterMethods)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public SieveProcessor(IOptions<SieveOptions> options, ISieveCustomSortMethods customSortMethods, ISieveCustomFilterMethods customFilterMethods) : base(options, customSortMethods, customFilterMethods)
|
public SieveProcessor(IOptions<SieveOptions> options, ISieveCustomSortMethods customSortMethods,
|
||||||
|
ISieveCustomFilterMethods customFilterMethods)
|
||||||
|
: base(options, customSortMethods, customFilterMethods)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -57,17 +67,17 @@ namespace Sieve.Services
|
|||||||
where TFilterTerm : IFilterTerm, new()
|
where TFilterTerm : IFilterTerm, new()
|
||||||
where TSortTerm : ISortTerm, new()
|
where TSortTerm : ISortTerm, new()
|
||||||
{
|
{
|
||||||
private const string nullFilterValue = "null";
|
private const string NullFilterValue = "null";
|
||||||
private readonly IOptions<SieveOptions> _options;
|
private readonly IOptions<SieveOptions> _options;
|
||||||
private readonly ISieveCustomSortMethods _customSortMethods;
|
private readonly ISieveCustomSortMethods _customSortMethods;
|
||||||
private readonly ISieveCustomFilterMethods _customFilterMethods;
|
private readonly ISieveCustomFilterMethods _customFilterMethods;
|
||||||
private readonly SievePropertyMapper mapper = new SievePropertyMapper();
|
private readonly SievePropertyMapper _mapper = new SievePropertyMapper();
|
||||||
|
|
||||||
public SieveProcessor(IOptions<SieveOptions> options,
|
public SieveProcessor(IOptions<SieveOptions> options,
|
||||||
ISieveCustomSortMethods customSortMethods,
|
ISieveCustomSortMethods customSortMethods,
|
||||||
ISieveCustomFilterMethods customFilterMethods)
|
ISieveCustomFilterMethods customFilterMethods)
|
||||||
{
|
{
|
||||||
mapper = MapProperties(mapper);
|
_mapper = MapProperties(_mapper);
|
||||||
_options = options;
|
_options = options;
|
||||||
_customSortMethods = customSortMethods;
|
_customSortMethods = customSortMethods;
|
||||||
_customFilterMethods = customFilterMethods;
|
_customFilterMethods = customFilterMethods;
|
||||||
@ -76,7 +86,7 @@ namespace Sieve.Services
|
|||||||
public SieveProcessor(IOptions<SieveOptions> options,
|
public SieveProcessor(IOptions<SieveOptions> options,
|
||||||
ISieveCustomSortMethods customSortMethods)
|
ISieveCustomSortMethods customSortMethods)
|
||||||
{
|
{
|
||||||
mapper = MapProperties(mapper);
|
_mapper = MapProperties(_mapper);
|
||||||
_options = options;
|
_options = options;
|
||||||
_customSortMethods = customSortMethods;
|
_customSortMethods = customSortMethods;
|
||||||
}
|
}
|
||||||
@ -84,14 +94,14 @@ namespace Sieve.Services
|
|||||||
public SieveProcessor(IOptions<SieveOptions> options,
|
public SieveProcessor(IOptions<SieveOptions> options,
|
||||||
ISieveCustomFilterMethods customFilterMethods)
|
ISieveCustomFilterMethods customFilterMethods)
|
||||||
{
|
{
|
||||||
mapper = MapProperties(mapper);
|
_mapper = MapProperties(_mapper);
|
||||||
_options = options;
|
_options = options;
|
||||||
_customFilterMethods = customFilterMethods;
|
_customFilterMethods = customFilterMethods;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SieveProcessor(IOptions<SieveOptions> options)
|
public SieveProcessor(IOptions<SieveOptions> options)
|
||||||
{
|
{
|
||||||
mapper = MapProperties(mapper);
|
_mapper = MapProperties(_mapper);
|
||||||
_options = options;
|
_options = options;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,12 +116,8 @@ namespace Sieve.Services
|
|||||||
/// <param name="applySorting">Should the data be sorted? Defaults to true.</param>
|
/// <param name="applySorting">Should the data be sorted? Defaults to true.</param>
|
||||||
/// <param name="applyPagination">Should the data be paginated? Defaults to true.</param>
|
/// <param name="applyPagination">Should the data be paginated? Defaults to true.</param>
|
||||||
/// <returns>Returns a transformed version of `source`</returns>
|
/// <returns>Returns a transformed version of `source`</returns>
|
||||||
public IQueryable<TEntity> Apply<TEntity>(
|
public IQueryable<TEntity> Apply<TEntity>(TSieveModel model, IQueryable<TEntity> source,
|
||||||
TSieveModel model,
|
object[] dataForCustomMethods = null, bool applyFiltering = true, bool applySorting = true,
|
||||||
IQueryable<TEntity> source,
|
|
||||||
object[] dataForCustomMethods = null,
|
|
||||||
bool applyFiltering = true,
|
|
||||||
bool applySorting = true,
|
|
||||||
bool applyPagination = true)
|
bool applyPagination = true)
|
||||||
{
|
{
|
||||||
var result = source;
|
var result = source;
|
||||||
@ -123,19 +129,16 @@ namespace Sieve.Services
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Filter
|
|
||||||
if (applyFiltering)
|
if (applyFiltering)
|
||||||
{
|
{
|
||||||
result = ApplyFiltering(model, result, dataForCustomMethods);
|
result = ApplyFiltering(model, result, dataForCustomMethods);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort
|
|
||||||
if (applySorting)
|
if (applySorting)
|
||||||
{
|
{
|
||||||
result = ApplySorting(model, result, dataForCustomMethods);
|
result = ApplySorting(model, result, dataForCustomMethods);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Paginate
|
|
||||||
if (applyPagination)
|
if (applyPagination)
|
||||||
{
|
{
|
||||||
result = ApplyPagination(model, result);
|
result = ApplyPagination(model, result);
|
||||||
@ -145,25 +148,21 @@ namespace Sieve.Services
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
if (_options.Value.ThrowExceptions)
|
if (!_options.Value.ThrowExceptions)
|
||||||
{
|
|
||||||
if (ex is SieveException)
|
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new SieveException(ex.Message, ex);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ex is SieveException)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new SieveException(ex.Message, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private IQueryable<TEntity> ApplyFiltering<TEntity>(
|
private IQueryable<TEntity> ApplyFiltering<TEntity>(TSieveModel model, IQueryable<TEntity> result,
|
||||||
TSieveModel model,
|
|
||||||
IQueryable<TEntity> result,
|
|
||||||
object[] dataForCustomMethods = null)
|
object[] dataForCustomMethods = null)
|
||||||
{
|
{
|
||||||
if (model?.GetFiltersParsed() == null)
|
if (model?.GetFiltersParsed() == null)
|
||||||
@ -181,25 +180,20 @@ namespace Sieve.Services
|
|||||||
var (fullPropertyName, property) = GetSieveProperty<TEntity>(false, true, filterTermName);
|
var (fullPropertyName, property) = GetSieveProperty<TEntity>(false, true, filterTermName);
|
||||||
if (property != null)
|
if (property != null)
|
||||||
{
|
{
|
||||||
Expression propertyValue = parameter;
|
if (filterTerm.Values == null)
|
||||||
Expression nullCheck = null;
|
|
||||||
var names = fullPropertyName.Split('.');
|
|
||||||
for (var i = 0; i < names.Length; i++)
|
|
||||||
{
|
{
|
||||||
propertyValue = Expression.PropertyOrField(propertyValue, names[i]);
|
continue;
|
||||||
|
|
||||||
if (i != names.Length - 1 && propertyValue.Type.IsNullable())
|
|
||||||
{
|
|
||||||
nullCheck = GenerateFilterNullCheckExpression(propertyValue, nullCheck);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filterTerm.Values == null) continue;
|
|
||||||
|
|
||||||
var converter = TypeDescriptor.GetConverter(property.PropertyType);
|
var converter = TypeDescriptor.GetConverter(property.PropertyType);
|
||||||
foreach (var filterTermValue in filterTerm.Values)
|
foreach (var filterTermValue in filterTerm.Values)
|
||||||
{
|
{
|
||||||
var isFilterTermValueNull = filterTermValue.ToLower() == nullFilterValue;
|
var (propertyValue, nullCheck) =
|
||||||
|
GetPropertyValueAndNullCheckExpression(parameter, fullPropertyName);
|
||||||
|
|
||||||
|
var isFilterTermValueNull =
|
||||||
|
IsFilterTermValueNull(propertyValue, filterTerm, filterTermValue);
|
||||||
|
|
||||||
var filterValue = isFilterTermValueNull
|
var filterValue = isFilterTermValueNull
|
||||||
? Expression.Constant(null, property.PropertyType)
|
? Expression.Constant(null, property.PropertyType)
|
||||||
: ConvertStringValueToConstantExpression(filterTermValue, property, converter);
|
: ConvertStringValueToConstantExpression(filterTermValue, property, converter);
|
||||||
@ -208,11 +202,11 @@ namespace Sieve.Services
|
|||||||
{
|
{
|
||||||
propertyValue = Expression.Call(propertyValue,
|
propertyValue = Expression.Call(propertyValue,
|
||||||
typeof(string).GetMethods()
|
typeof(string).GetMethods()
|
||||||
.First(m => m.Name == "ToUpper" && m.GetParameters().Length == 0));
|
.First(m => m.Name == "ToUpper" && m.GetParameters().Length == 0));
|
||||||
|
|
||||||
filterValue = Expression.Call(filterValue,
|
filterValue = Expression.Call(filterValue,
|
||||||
typeof(string).GetMethods()
|
typeof(string).GetMethods()
|
||||||
.First(m => m.Name == "ToUpper" && m.GetParameters().Length == 0));
|
.First(m => m.Name == "ToUpper" && m.GetParameters().Length == 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
var expression = GetExpression(filterTerm, filterValue, propertyValue);
|
var expression = GetExpression(filterTerm, filterValue, propertyValue);
|
||||||
@ -222,60 +216,97 @@ namespace Sieve.Services
|
|||||||
expression = Expression.Not(expression);
|
expression = Expression.Not(expression);
|
||||||
}
|
}
|
||||||
|
|
||||||
var filterValueNullCheck = !isFilterTermValueNull && propertyValue.Type.IsNullable()
|
var filterValueNullCheck = GetFilterValueNullCheck(parameter, fullPropertyName, isFilterTermValueNull);
|
||||||
? GenerateFilterNullCheckExpression(propertyValue, nullCheck)
|
|
||||||
: nullCheck;
|
|
||||||
|
|
||||||
if (filterValueNullCheck != null)
|
if (filterValueNullCheck != null)
|
||||||
{
|
{
|
||||||
expression = Expression.AndAlso(filterValueNullCheck, expression);
|
expression = Expression.AndAlso(filterValueNullCheck, expression);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (innerExpression == null)
|
innerExpression = innerExpression == null
|
||||||
{
|
? expression
|
||||||
innerExpression = expression;
|
: Expression.OrElse(innerExpression, expression);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
innerExpression = Expression.OrElse(innerExpression, expression);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
result = ApplyCustomMethod(result, filterTermName, _customFilterMethods,
|
result = ApplyCustomMethod(result, filterTermName, _customFilterMethods,
|
||||||
new object[] {
|
new object[] {result, filterTerm.Operator, filterTerm.Values}, dataForCustomMethods);
|
||||||
result,
|
|
||||||
filterTerm.Operator,
|
|
||||||
filterTerm.Values
|
|
||||||
}, dataForCustomMethods);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (outerExpression == null)
|
if (outerExpression == null)
|
||||||
{
|
{
|
||||||
outerExpression = innerExpression;
|
outerExpression = innerExpression;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (innerExpression == null)
|
if (innerExpression == null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
outerExpression = Expression.AndAlso(outerExpression, innerExpression);
|
outerExpression = Expression.AndAlso(outerExpression, innerExpression);
|
||||||
}
|
}
|
||||||
|
|
||||||
return outerExpression == null
|
return outerExpression == null
|
||||||
? result
|
? result
|
||||||
: result.Where(Expression.Lambda<Func<TEntity, bool>>(outerExpression, parameter));
|
: result.Where(Expression.Lambda<Func<TEntity, bool>>(outerExpression, parameter));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Expression GenerateFilterNullCheckExpression(Expression propertyValue, Expression nullCheckExpression)
|
private static Expression GetFilterValueNullCheck(Expression parameter, string fullPropertyName,
|
||||||
|
bool isFilterTermValueNull)
|
||||||
|
{
|
||||||
|
var (propertyValue, nullCheck) = GetPropertyValueAndNullCheckExpression(parameter, fullPropertyName);
|
||||||
|
|
||||||
|
if (!isFilterTermValueNull && propertyValue.Type.IsNullable())
|
||||||
|
{
|
||||||
|
return GenerateFilterNullCheckExpression(propertyValue, nullCheck);
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullCheck;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsFilterTermValueNull(Expression propertyValue, TFilterTerm filterTerm,
|
||||||
|
string filterTermValue)
|
||||||
|
{
|
||||||
|
var isNotString = propertyValue.Type != typeof(string);
|
||||||
|
|
||||||
|
var isValidStringNullOperation = filterTerm.OperatorParsed == FilterOperator.Equals ||
|
||||||
|
filterTerm.OperatorParsed == FilterOperator.NotEquals;
|
||||||
|
|
||||||
|
return filterTermValue.ToLower() == NullFilterValue && (isNotString || isValidStringNullOperation);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (Expression propertyValue, Expression nullCheck) GetPropertyValueAndNullCheckExpression(
|
||||||
|
Expression parameter, string fullPropertyName)
|
||||||
|
{
|
||||||
|
var propertyValue = parameter;
|
||||||
|
Expression nullCheck = null;
|
||||||
|
var names = fullPropertyName.Split('.');
|
||||||
|
for (var i = 0; i < names.Length; i++)
|
||||||
|
{
|
||||||
|
propertyValue = Expression.PropertyOrField(propertyValue, names[i]);
|
||||||
|
|
||||||
|
if (i != names.Length - 1 && propertyValue.Type.IsNullable())
|
||||||
|
{
|
||||||
|
nullCheck = GenerateFilterNullCheckExpression(propertyValue, nullCheck);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (propertyValue, nullCheck);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Expression GenerateFilterNullCheckExpression(Expression propertyValue,
|
||||||
|
Expression nullCheckExpression)
|
||||||
{
|
{
|
||||||
return nullCheckExpression == null
|
return nullCheckExpression == null
|
||||||
? Expression.NotEqual(propertyValue, Expression.Default(propertyValue.Type))
|
? Expression.NotEqual(propertyValue, Expression.Default(propertyValue.Type))
|
||||||
: Expression.AndAlso(nullCheckExpression, Expression.NotEqual(propertyValue, Expression.Default(propertyValue.Type)));
|
: Expression.AndAlso(nullCheckExpression,
|
||||||
|
Expression.NotEqual(propertyValue, Expression.Default(propertyValue.Type)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Expression ConvertStringValueToConstantExpression(string value, PropertyInfo property, TypeConverter converter)
|
private static Expression ConvertStringValueToConstantExpression(string value, PropertyInfo property,
|
||||||
|
TypeConverter converter)
|
||||||
{
|
{
|
||||||
dynamic constantVal = converter.CanConvertFrom(typeof(string))
|
dynamic constantVal = converter.CanConvertFrom(typeof(string))
|
||||||
? converter.ConvertFrom(value)
|
? converter.ConvertFrom(value)
|
||||||
@ -286,47 +317,34 @@ namespace Sieve.Services
|
|||||||
|
|
||||||
private static Expression GetExpression(TFilterTerm filterTerm, dynamic filterValue, dynamic propertyValue)
|
private static Expression GetExpression(TFilterTerm filterTerm, dynamic filterValue, dynamic propertyValue)
|
||||||
{
|
{
|
||||||
switch (filterTerm.OperatorParsed)
|
return filterTerm.OperatorParsed switch
|
||||||
{
|
{
|
||||||
case FilterOperator.Equals:
|
FilterOperator.Equals => Expression.Equal(propertyValue, filterValue),
|
||||||
return Expression.Equal(propertyValue, filterValue);
|
FilterOperator.NotEquals => Expression.NotEqual(propertyValue, filterValue),
|
||||||
case FilterOperator.NotEquals:
|
FilterOperator.GreaterThan => Expression.GreaterThan(propertyValue, filterValue),
|
||||||
return Expression.NotEqual(propertyValue, filterValue);
|
FilterOperator.LessThan => Expression.LessThan(propertyValue, filterValue),
|
||||||
case FilterOperator.GreaterThan:
|
FilterOperator.GreaterThanOrEqualTo => Expression.GreaterThanOrEqual(propertyValue, filterValue),
|
||||||
return Expression.GreaterThan(propertyValue, filterValue);
|
FilterOperator.LessThanOrEqualTo => Expression.LessThanOrEqual(propertyValue, filterValue),
|
||||||
case FilterOperator.LessThan:
|
FilterOperator.Contains => Expression.Call(propertyValue,
|
||||||
return Expression.LessThan(propertyValue, filterValue);
|
typeof(string).GetMethods().First(m => m.Name == "Contains" && m.GetParameters().Length == 1),
|
||||||
case FilterOperator.GreaterThanOrEqualTo:
|
filterValue),
|
||||||
return Expression.GreaterThanOrEqual(propertyValue, filterValue);
|
FilterOperator.StartsWith => Expression.Call(propertyValue,
|
||||||
case FilterOperator.LessThanOrEqualTo:
|
typeof(string).GetMethods().First(m => m.Name == "StartsWith" && m.GetParameters().Length == 1),
|
||||||
return Expression.LessThanOrEqual(propertyValue, filterValue);
|
filterValue),
|
||||||
case FilterOperator.Contains:
|
_ => Expression.Equal(propertyValue, filterValue)
|
||||||
return Expression.Call(propertyValue,
|
};
|
||||||
typeof(string).GetMethods()
|
|
||||||
.First(m => m.Name == "Contains" && m.GetParameters().Length == 1),
|
|
||||||
filterValue);
|
|
||||||
case FilterOperator.StartsWith:
|
|
||||||
return Expression.Call(propertyValue,
|
|
||||||
typeof(string).GetMethods()
|
|
||||||
.First(m => m.Name == "StartsWith" && m.GetParameters().Length == 1),
|
|
||||||
filterValue);
|
|
||||||
default:
|
|
||||||
return Expression.Equal(propertyValue, filterValue);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Workaround to ensure that the filter value gets passed as a parameter in generated SQL from EF Core
|
// Workaround to ensure that the filter value gets passed as a parameter in generated SQL from EF Core
|
||||||
// See https://github.com/aspnet/EntityFrameworkCore/issues/3361
|
// See https://github.com/aspnet/EntityFrameworkCore/issues/3361
|
||||||
// Expression.Constant passed the target type to allow Nullable comparison
|
// Expression.Constant passed the target type to allow Nullable comparison
|
||||||
// See http://bradwilson.typepad.com/blog/2008/07/creating-nullab.html
|
// See http://bradwilson.typepad.com/blog/2008/07/creating-nullab.html
|
||||||
private Expression GetClosureOverConstant<T>(T constant, Type targetType)
|
private static Expression GetClosureOverConstant<T>(T constant, Type targetType)
|
||||||
{
|
{
|
||||||
return Expression.Constant(constant, targetType);
|
return Expression.Constant(constant, targetType);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IQueryable<TEntity> ApplySorting<TEntity>(
|
private IQueryable<TEntity> ApplySorting<TEntity>(TSieveModel model, IQueryable<TEntity> result,
|
||||||
TSieveModel model,
|
|
||||||
IQueryable<TEntity> result,
|
|
||||||
object[] dataForCustomMethods = null)
|
object[] dataForCustomMethods = null)
|
||||||
{
|
{
|
||||||
if (model?.GetSortsParsed() == null)
|
if (model?.GetSortsParsed() == null)
|
||||||
@ -346,33 +364,29 @@ namespace Sieve.Services
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
result = ApplyCustomMethod(result, sortTerm.Name, _customSortMethods,
|
result = ApplyCustomMethod(result, sortTerm.Name, _customSortMethods,
|
||||||
new object[]
|
new object[] {result, useThenBy, sortTerm.Descending}, dataForCustomMethods);
|
||||||
{
|
|
||||||
result,
|
|
||||||
useThenBy,
|
|
||||||
sortTerm.Descending
|
|
||||||
}, dataForCustomMethods);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
useThenBy = true;
|
useThenBy = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IQueryable<TEntity> ApplyPagination<TEntity>(
|
private IQueryable<TEntity> ApplyPagination<TEntity>(TSieveModel model, IQueryable<TEntity> result)
|
||||||
TSieveModel model,
|
|
||||||
IQueryable<TEntity> result)
|
|
||||||
{
|
{
|
||||||
var page = model?.Page ?? 1;
|
var page = model?.Page ?? 1;
|
||||||
var pageSize = model?.PageSize ?? _options.Value.DefaultPageSize;
|
var pageSize = model?.PageSize ?? _options.Value.DefaultPageSize;
|
||||||
var maxPageSize = _options.Value.MaxPageSize > 0 ? _options.Value.MaxPageSize : pageSize;
|
var maxPageSize = _options.Value.MaxPageSize > 0 ? _options.Value.MaxPageSize : pageSize;
|
||||||
|
|
||||||
if (pageSize > 0)
|
if (pageSize <= 0)
|
||||||
{
|
{
|
||||||
result = result.Skip((page - 1) * pageSize);
|
return result;
|
||||||
result = result.Take(Math.Min(pageSize, maxPageSize));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result = result.Skip((page - 1) * pageSize);
|
||||||
|
result = result.Take(Math.Min(pageSize, maxPageSize));
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -381,51 +395,52 @@ namespace Sieve.Services
|
|||||||
return mapper;
|
return mapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
private (string, PropertyInfo) GetSieveProperty<TEntity>(
|
private (string, PropertyInfo) GetSieveProperty<TEntity>(bool canSortRequired, bool canFilterRequired,
|
||||||
bool canSortRequired,
|
|
||||||
bool canFilterRequired,
|
|
||||||
string name)
|
string name)
|
||||||
{
|
{
|
||||||
var property = mapper.FindProperty<TEntity>(canSortRequired, canFilterRequired, name, _options.Value.CaseSensitive);
|
var property = _mapper.FindProperty<TEntity>(canSortRequired, canFilterRequired, name,
|
||||||
if (property.Item1 == null)
|
_options.Value.CaseSensitive);
|
||||||
|
if (property.Item1 != null)
|
||||||
{
|
{
|
||||||
var prop = FindPropertyBySieveAttribute<TEntity>(canSortRequired, canFilterRequired, name, _options.Value.CaseSensitive);
|
return property;
|
||||||
return (prop?.Name, prop);
|
|
||||||
}
|
}
|
||||||
return property;
|
|
||||||
|
|
||||||
|
var prop = FindPropertyBySieveAttribute<TEntity>(canSortRequired, canFilterRequired, name,
|
||||||
|
_options.Value.CaseSensitive);
|
||||||
|
return (prop?.Name, prop);
|
||||||
}
|
}
|
||||||
|
|
||||||
private PropertyInfo FindPropertyBySieveAttribute<TEntity>(
|
private static PropertyInfo FindPropertyBySieveAttribute<TEntity>(bool canSortRequired, bool canFilterRequired,
|
||||||
bool canSortRequired,
|
string name, bool isCaseSensitive)
|
||||||
bool canFilterRequired,
|
|
||||||
string name,
|
|
||||||
bool isCaseSensitive)
|
|
||||||
{
|
{
|
||||||
return Array.Find(typeof(TEntity).GetProperties(), p =>
|
return Array.Find(typeof(TEntity).GetProperties(),
|
||||||
{
|
p => p.GetCustomAttribute(typeof(SieveAttribute)) is SieveAttribute SieveAttribute
|
||||||
return p.GetCustomAttribute(typeof(SieveAttribute)) is SieveAttribute sieveAttribute
|
&& (!canSortRequired || SieveAttribute.CanSort)
|
||||||
&& (!canSortRequired || sieveAttribute.CanSort)
|
&& (!canFilterRequired || SieveAttribute.CanFilter)
|
||||||
&& (!canFilterRequired || sieveAttribute.CanFilter)
|
&& (SieveAttribute.Name ?? p.Name).Equals(name,
|
||||||
&& (sieveAttribute.Name ?? p.Name).Equals(name, isCaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase);
|
isCaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private IQueryable<TEntity> ApplyCustomMethod<TEntity>(IQueryable<TEntity> result, string name, object parent, object[] parameters, object[] optionalParameters = null)
|
private IQueryable<TEntity> ApplyCustomMethod<TEntity>(IQueryable<TEntity> result, string name, object parent,
|
||||||
|
object[] parameters, object[] optionalParameters = null)
|
||||||
{
|
{
|
||||||
var customMethod = parent?.GetType()
|
var customMethod = parent?.GetType()
|
||||||
.GetMethodExt(name,
|
.GetMethodExt(name,
|
||||||
_options.Value.CaseSensitive ? BindingFlags.Default : BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance,
|
_options.Value.CaseSensitive
|
||||||
typeof(IQueryable<TEntity>));
|
? BindingFlags.Default
|
||||||
|
: BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance,
|
||||||
|
typeof(IQueryable<TEntity>));
|
||||||
|
|
||||||
|
|
||||||
if (customMethod == null)
|
if (customMethod == null)
|
||||||
{
|
{
|
||||||
// Find generic methods `public IQueryable<T> Filter<T>(IQueryable<T> source, ...)`
|
// Find generic methods `public IQueryable<T> Filter<T>(IQueryable<T> source, ...)`
|
||||||
var genericCustomMethod = parent?.GetType()
|
var genericCustomMethod = parent?.GetType()
|
||||||
.GetMethodExt(name,
|
.GetMethodExt(name,
|
||||||
_options.Value.CaseSensitive ? BindingFlags.Default : BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance,
|
_options.Value.CaseSensitive
|
||||||
typeof(IQueryable<>));
|
? BindingFlags.Default
|
||||||
|
: BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance,
|
||||||
|
typeof(IQueryable<>));
|
||||||
|
|
||||||
if (genericCustomMethod != null &&
|
if (genericCustomMethod != null &&
|
||||||
genericCustomMethod.ReturnType.IsGenericType &&
|
genericCustomMethod.ReturnType.IsGenericType &&
|
||||||
@ -433,7 +448,8 @@ namespace Sieve.Services
|
|||||||
{
|
{
|
||||||
var genericBaseType = genericCustomMethod.ReturnType.GenericTypeArguments[0];
|
var genericBaseType = genericCustomMethod.ReturnType.GenericTypeArguments[0];
|
||||||
var constraints = genericBaseType.GetGenericParameterConstraints();
|
var constraints = genericBaseType.GetGenericParameterConstraints();
|
||||||
if (constraints == null || constraints.Length == 0 || constraints.All((t) => t.IsAssignableFrom(typeof(TEntity))))
|
if (constraints == null || constraints.Length == 0 ||
|
||||||
|
constraints.All((t) => t.IsAssignableFrom(typeof(TEntity))))
|
||||||
{
|
{
|
||||||
customMethod = genericCustomMethod.MakeGenericMethod(typeof(TEntity));
|
customMethod = genericCustomMethod.MakeGenericMethod(typeof(TEntity));
|
||||||
}
|
}
|
||||||
@ -462,40 +478,34 @@ namespace Sieve.Services
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var incompatibleCustomMethods = parent?
|
var incompatibleCustomMethods =
|
||||||
.GetType()
|
parent?
|
||||||
.GetMethods
|
.GetType()
|
||||||
(
|
.GetMethods(_options.Value.CaseSensitive
|
||||||
_options.Value.CaseSensitive
|
? BindingFlags.Default
|
||||||
? BindingFlags.Default
|
: BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance)
|
||||||
: BindingFlags.IgnoreCase | BindingFlags.Public |
|
.Where(method => string.Equals(method.Name, name,
|
||||||
BindingFlags.Instance
|
_options.Value.CaseSensitive
|
||||||
)
|
? StringComparison.InvariantCulture
|
||||||
.Where(method => string.Equals(method.Name, name,
|
: StringComparison.InvariantCultureIgnoreCase))
|
||||||
_options.Value.CaseSensitive
|
.ToList()
|
||||||
? StringComparison.InvariantCulture
|
?? new List<MethodInfo>();
|
||||||
: StringComparison.InvariantCultureIgnoreCase))
|
|
||||||
.ToList()
|
|
||||||
??
|
|
||||||
new List<MethodInfo>();
|
|
||||||
|
|
||||||
if (incompatibleCustomMethods.Any())
|
if (!incompatibleCustomMethods.Any())
|
||||||
{
|
|
||||||
var incompatibles =
|
|
||||||
from incompatibleCustomMethod in incompatibleCustomMethods
|
|
||||||
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}");
|
|
||||||
|
|
||||||
var aggregate = new AggregateException(incompatibles);
|
|
||||||
|
|
||||||
throw new SieveIncompatibleMethodException(aggregate.Message, aggregate);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
throw new SieveMethodNotFoundException(name, $"{name} not found.");
|
throw new SieveMethodNotFoundException(name, $"{name} not found.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var incompatibles =
|
||||||
|
from incompatibleCustomMethod in incompatibleCustomMethods
|
||||||
|
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}");
|
||||||
|
|
||||||
|
var aggregate = new AggregateException(incompatibles);
|
||||||
|
|
||||||
|
throw new SieveIncompatibleMethodException(aggregate.Message, aggregate);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -1,22 +1,25 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.1</TargetFramework>
|
||||||
<Version>2.3.3</Version>
|
<Description>Sieve is a simple, clean, and extensible framework for .NET Core that adds sorting, filtering, and pagination functionality out of the box. Most common use case would be for serving ASP.NET Core GET queries. Documentation available on GitHub: https://github.com/Biarity/Sieve/</Description>
|
||||||
<Description>Sieve is a simple, clean, and extensible framework for .NET Core that adds sorting, filtering, and pagination functionality out of the box. Most common use case would be for serving ASP.NET Core GET queries. Documentation available on GitHub: https://github.com/Biarity/Sieve/
|
<Authors>2018 Biarity, 2021 Kevin Sommer</Authors>
|
||||||
</Description>
|
|
||||||
<Copyright>Copyright 2018</Copyright>
|
<PackageTags>Filter;Sort;Page;Paging;</PackageTags>
|
||||||
<PackageLicenseUrl>https://github.com/Biarity/Sieve/blob/master/LICENSE</PackageLicenseUrl>
|
<PackageIcon>icon.png</PackageIcon>
|
||||||
<PackageProjectUrl>https://github.com/Biarity/Sieve</PackageProjectUrl>
|
<PackageProjectUrl>https://github.com/Biarity/Sieve</PackageProjectUrl>
|
||||||
<PackageIconUrl>https://emojipedia-us.s3.amazonaws.com/thumbs/240/twitter/120/alembic_2697.png</PackageIconUrl>
|
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
|
||||||
<RepositoryUrl></RepositoryUrl>
|
|
||||||
<PackageReleaseNotes>Only Skip when pageSize > 0 (#63)
|
|
||||||
Added support for generic filter and sort methods (#60)
|
|
||||||
Don't process when filterTerm.Values is null (#59)
|
|
||||||
</PackageReleaseNotes>
|
|
||||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
|
||||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||||
<Authors>Biarity</Authors>
|
|
||||||
|
<RepositoryUrl>https://github.com/Biarity/Sieve</RepositoryUrl>
|
||||||
|
<RepositoryType>git</RepositoryType>
|
||||||
|
|
||||||
|
<!-- Declare that the Repository URL can be published to NuSpec -->
|
||||||
|
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||||
|
<!-- Embed source files that are not tracked by the source control manager to the PDB -->
|
||||||
|
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
||||||
|
<!-- Include PDB in the built .nupkg -->
|
||||||
|
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -1,44 +0,0 @@
|
|||||||
// <auto-generated />
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
|
||||||
using Microsoft.EntityFrameworkCore.Metadata;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
using Microsoft.EntityFrameworkCore.Storage;
|
|
||||||
using Microsoft.EntityFrameworkCore.Storage.Internal;
|
|
||||||
using SieveTests.Entities;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace SieveTests.Migrations
|
|
||||||
{
|
|
||||||
[DbContext(typeof(ApplicationDbContext))]
|
|
||||||
[Migration("20180127005347_Init")]
|
|
||||||
partial class Init
|
|
||||||
{
|
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
|
||||||
{
|
|
||||||
#pragma warning disable 612, 618
|
|
||||||
modelBuilder
|
|
||||||
.HasAnnotation("ProductVersion", "2.0.1-rtm-125")
|
|
||||||
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
|
|
||||||
|
|
||||||
modelBuilder.Entity("SieveTests.Entities.Post", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd();
|
|
||||||
|
|
||||||
b.Property<int>("CommentCount");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("DateCreated");
|
|
||||||
|
|
||||||
b.Property<int>("LikeCount");
|
|
||||||
|
|
||||||
b.Property<string>("Title");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("Posts");
|
|
||||||
});
|
|
||||||
#pragma warning restore 612, 618
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
// <auto-generated />
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
|
||||||
using Microsoft.EntityFrameworkCore.Metadata;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
using Microsoft.EntityFrameworkCore.Storage;
|
|
||||||
using Microsoft.EntityFrameworkCore.Storage.Internal;
|
|
||||||
using SieveTests.Entities;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace SieveTests.Migrations
|
|
||||||
{
|
|
||||||
[DbContext(typeof(ApplicationDbContext))]
|
|
||||||
[Migration("20180522013323_AddDateLastViewedColumn")]
|
|
||||||
partial class AddDateLastViewedColumn
|
|
||||||
{
|
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
|
||||||
{
|
|
||||||
#pragma warning disable 612, 618
|
|
||||||
modelBuilder
|
|
||||||
.HasAnnotation("ProductVersion", "2.0.1-rtm-125")
|
|
||||||
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
|
|
||||||
|
|
||||||
modelBuilder.Entity("SieveTests.Entities.Post", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd();
|
|
||||||
|
|
||||||
b.Property<int>("CommentCount");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("DateCreated");
|
|
||||||
|
|
||||||
b.Property<DateTime>("DateLastViewed")
|
|
||||||
.HasColumnType("datetime");
|
|
||||||
|
|
||||||
b.Property<int>("LikeCount");
|
|
||||||
|
|
||||||
b.Property<string>("Title");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("Posts");
|
|
||||||
});
|
|
||||||
#pragma warning restore 612, 618
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
namespace SieveTests.Migrations
|
|
||||||
{
|
|
||||||
public partial class AddDateLastViewedColumn : Migration
|
|
||||||
{
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.AddColumn<DateTime>(
|
|
||||||
name: "DateLastViewed",
|
|
||||||
table: "Posts",
|
|
||||||
type: "datetime",
|
|
||||||
nullable: false,
|
|
||||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropColumn(
|
|
||||||
name: "DateLastViewed",
|
|
||||||
table: "Posts");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
// <auto-generated />
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
|
||||||
using Microsoft.EntityFrameworkCore.Metadata;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
using Microsoft.EntityFrameworkCore.Storage;
|
|
||||||
using Microsoft.EntityFrameworkCore.Storage.Internal;
|
|
||||||
using SieveTests.Entities;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace SieveTests.Migrations
|
|
||||||
{
|
|
||||||
[DbContext(typeof(ApplicationDbContext))]
|
|
||||||
partial class ApplicationDbContextModelSnapshot : ModelSnapshot
|
|
||||||
{
|
|
||||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
|
||||||
{
|
|
||||||
#pragma warning disable 612, 618
|
|
||||||
modelBuilder
|
|
||||||
.HasAnnotation("ProductVersion", "2.0.1-rtm-125")
|
|
||||||
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
|
|
||||||
|
|
||||||
modelBuilder.Entity("SieveTests.Entities.Post", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd();
|
|
||||||
|
|
||||||
b.Property<int>("CommentCount");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("DateCreated");
|
|
||||||
|
|
||||||
b.Property<DateTime>("DateLastViewed")
|
|
||||||
.HasColumnType("datetime");
|
|
||||||
|
|
||||||
b.Property<int>("LikeCount");
|
|
||||||
|
|
||||||
b.Property<int?>("CategoryId");
|
|
||||||
|
|
||||||
b.Property<string>("Title");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("Posts");
|
|
||||||
});
|
|
||||||
#pragma warning restore 612, 618
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Compile Remove="Migrations\20180127005211_Itit.cs" />
|
|
||||||
<Compile Remove="Migrations\20180127005211_Itit.Designer.cs" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Folder Include="wwwroot\" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.9" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.1" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\SieveUnitTests\SieveUnitTests.csproj" />
|
|
||||||
<ProjectReference Include="..\Sieve\Sieve.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
@ -1,67 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
|
||||||
using Microsoft.AspNetCore.Hosting;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Sieve.Models;
|
|
||||||
using Sieve.Services;
|
|
||||||
using SieveTests.Entities;
|
|
||||||
using SieveTests.Services;
|
|
||||||
|
|
||||||
namespace SieveTests
|
|
||||||
{
|
|
||||||
public class Startup
|
|
||||||
{
|
|
||||||
public Startup(IConfiguration configuration)
|
|
||||||
{
|
|
||||||
Configuration = configuration;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IConfiguration Configuration { get; }
|
|
||||||
|
|
||||||
// This method gets called by the runtime. Use this method to add services to the container.
|
|
||||||
public void ConfigureServices(IServiceCollection services)
|
|
||||||
{
|
|
||||||
services.AddMvc();
|
|
||||||
|
|
||||||
services.AddDbContext<ApplicationDbContext>(options =>
|
|
||||||
options.UseSqlServer(Configuration.GetConnectionString("TestSqlServer")));
|
|
||||||
|
|
||||||
services.Configure<SieveOptions>(Configuration.GetSection("Sieve"));
|
|
||||||
|
|
||||||
services.AddScoped<ISieveCustomSortMethods, SieveCustomSortMethods>();
|
|
||||||
services.AddScoped<ISieveCustomFilterMethods, SieveCustomFilterMethods>();
|
|
||||||
services.AddScoped<ISieveProcessor, ApplicationSieveProcessor>();
|
|
||||||
}
|
|
||||||
|
|
||||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
|
||||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
|
|
||||||
{
|
|
||||||
// TIME MEASUREMENT
|
|
||||||
var times = new List<long>();
|
|
||||||
app.Use(async (context, next) =>
|
|
||||||
{
|
|
||||||
var sw = new Stopwatch();
|
|
||||||
sw.Start();
|
|
||||||
await next.Invoke();
|
|
||||||
sw.Stop();
|
|
||||||
times.Add(sw.ElapsedMilliseconds);
|
|
||||||
var text = $"AVG: {(int)times.Average()}ms; AT {sw.ElapsedMilliseconds}; COUNT: {times.Count()}";
|
|
||||||
Console.WriteLine(text);
|
|
||||||
await context.Response.WriteAsync($"<!-- {text} -->");
|
|
||||||
});
|
|
||||||
|
|
||||||
if (env.IsDevelopment())
|
|
||||||
{
|
|
||||||
app.UseDeveloperExceptionPage();
|
|
||||||
}
|
|
||||||
|
|
||||||
app.UseMvc();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,5 +7,8 @@ namespace SieveUnitTests.Entities
|
|||||||
{
|
{
|
||||||
[Sieve(CanFilter = true)]
|
[Sieve(CanFilter = true)]
|
||||||
public string Text { get; set; }
|
public string Text { get; set; }
|
||||||
|
|
||||||
|
[Sieve(CanFilter = true)]
|
||||||
|
public string Author { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,32 +1,34 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
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;
|
||||||
|
using Xunit;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
namespace SieveUnitTests
|
namespace SieveUnitTests
|
||||||
{
|
{
|
||||||
[TestClass]
|
|
||||||
public class General
|
public class General
|
||||||
{
|
{
|
||||||
|
private readonly ITestOutputHelper _testOutputHelper;
|
||||||
private readonly SieveProcessor _processor;
|
private readonly SieveProcessor _processor;
|
||||||
private readonly IQueryable<Post> _posts;
|
private readonly IQueryable<Post> _posts;
|
||||||
private readonly IQueryable<Comment> _comments;
|
private readonly IQueryable<Comment> _comments;
|
||||||
|
|
||||||
public General()
|
public General(ITestOutputHelper testOutputHelper)
|
||||||
{
|
{
|
||||||
|
_testOutputHelper = testOutputHelper;
|
||||||
_processor = new ApplicationSieveProcessor(new SieveOptionsAccessor(),
|
_processor = new ApplicationSieveProcessor(new SieveOptionsAccessor(),
|
||||||
new SieveCustomSortMethods(),
|
new SieveCustomSortMethods(),
|
||||||
new SieveCustomFilterMethods());
|
new SieveCustomFilterMethods());
|
||||||
|
|
||||||
_posts = new List<Post>
|
_posts = new List<Post>
|
||||||
{
|
{
|
||||||
new Post() {
|
new Post
|
||||||
|
{
|
||||||
Id = 0,
|
Id = 0,
|
||||||
Title = "A",
|
Title = "A",
|
||||||
LikeCount = 100,
|
LikeCount = 100,
|
||||||
@ -35,7 +37,8 @@ namespace SieveUnitTests
|
|||||||
TopComment = new Comment { Id = 0, Text = "A1" },
|
TopComment = new Comment { Id = 0, Text = "A1" },
|
||||||
FeaturedComment = new Comment { Id = 4, Text = "A2" }
|
FeaturedComment = new Comment { Id = 4, Text = "A2" }
|
||||||
},
|
},
|
||||||
new Post() {
|
new Post
|
||||||
|
{
|
||||||
Id = 1,
|
Id = 1,
|
||||||
Title = "B",
|
Title = "B",
|
||||||
LikeCount = 50,
|
LikeCount = 50,
|
||||||
@ -44,7 +47,8 @@ namespace SieveUnitTests
|
|||||||
TopComment = new Comment { Id = 3, Text = "B1" },
|
TopComment = new Comment { Id = 3, Text = "B1" },
|
||||||
FeaturedComment = new Comment { Id = 5, Text = "B2" }
|
FeaturedComment = new Comment { Id = 5, Text = "B2" }
|
||||||
},
|
},
|
||||||
new Post() {
|
new Post
|
||||||
|
{
|
||||||
Id = 2,
|
Id = 2,
|
||||||
Title = "C",
|
Title = "C",
|
||||||
LikeCount = 0,
|
LikeCount = 0,
|
||||||
@ -52,7 +56,8 @@ namespace SieveUnitTests
|
|||||||
TopComment = new Comment { Id = 2, Text = "C1" },
|
TopComment = new Comment { Id = 2, Text = "C1" },
|
||||||
FeaturedComment = new Comment { Id = 6, Text = "C2" }
|
FeaturedComment = new Comment { Id = 6, Text = "C2" }
|
||||||
},
|
},
|
||||||
new Post() {
|
new Post
|
||||||
|
{
|
||||||
Id = 3,
|
Id = 3,
|
||||||
Title = "D",
|
Title = "D",
|
||||||
LikeCount = 3,
|
LikeCount = 3,
|
||||||
@ -65,281 +70,301 @@ namespace SieveUnitTests
|
|||||||
|
|
||||||
_comments = new List<Comment>
|
_comments = new List<Comment>
|
||||||
{
|
{
|
||||||
new Comment() {
|
new Comment
|
||||||
|
{
|
||||||
Id = 0,
|
Id = 0,
|
||||||
DateCreated = DateTimeOffset.UtcNow.AddDays(-20),
|
DateCreated = DateTimeOffset.UtcNow.AddDays(-20),
|
||||||
Text = "This is an old comment."
|
Text = "This is an old comment."
|
||||||
},
|
},
|
||||||
new Comment() {
|
new Comment
|
||||||
|
{
|
||||||
Id = 1,
|
Id = 1,
|
||||||
DateCreated = DateTimeOffset.UtcNow.AddDays(-1),
|
DateCreated = DateTimeOffset.UtcNow.AddDays(-1),
|
||||||
Text = "This is a fairly new comment."
|
Text = "This is a fairly new comment."
|
||||||
},
|
},
|
||||||
new Comment() {
|
new Comment
|
||||||
|
{
|
||||||
Id = 2,
|
Id = 2,
|
||||||
DateCreated = DateTimeOffset.UtcNow,
|
DateCreated = DateTimeOffset.UtcNow,
|
||||||
Text = "This is a brand new comment. (Text in braces)"
|
Text = "This is a brand new comment. (Text in braces, comma separated)"
|
||||||
},
|
},
|
||||||
}.AsQueryable();
|
}.AsQueryable();
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void ContainsCanBeCaseInsensitive()
|
public void ContainsCanBeCaseInsensitive()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "Title@=*a"
|
Filters = "Title@=*a"
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, _posts);
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
Assert.AreEqual(result.First().Id, 0);
|
Assert.Equal(0, result.First().Id);
|
||||||
Assert.IsTrue(result.Count() == 1);
|
Assert.True(result.Count() == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void NotEqualsCanBeCaseInsensitive()
|
public void NotEqualsCanBeCaseInsensitive()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "Title!=*a"
|
Filters = "Title!=*a"
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, _posts);
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
Assert.AreEqual(result.First().Id, 1);
|
Assert.Equal(1, result.First().Id);
|
||||||
Assert.IsTrue(result.Count() == 3);
|
Assert.True(result.Count() == 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void ContainsIsCaseSensitive()
|
public void ContainsIsCaseSensitive()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "Title@=a",
|
Filters = "Title@=a",
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, _posts);
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
Assert.IsTrue(result.Count() == 0);
|
Assert.True(!result.Any());
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void NotContainsWorks()
|
public void NotContainsWorks()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "Title!@=D",
|
Filters = "Title!@=D",
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, _posts);
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
Assert.IsTrue(result.Count() == 3);
|
Assert.True(result.Count() == 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void CanFilterBools()
|
public void CanFilterBools()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "IsDraft==false"
|
Filters = "IsDraft==false"
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, _posts);
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
Assert.IsTrue(result.Count() == 2);
|
Assert.True(result.Count() == 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void CanSortBools()
|
public void CanSortBools()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Sorts = "-IsDraft"
|
Sorts = "-IsDraft"
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, _posts);
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
Assert.AreEqual(result.First().Id, 0);
|
Assert.Equal(0, result.First().Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void CanFilterNullableInts()
|
public void CanFilterNullableInts()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "CategoryId==1"
|
Filters = "CategoryId==1"
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, _posts);
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
Assert.IsTrue(result.Count() == 2);
|
Assert.True(result.Count() == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(@"Text@=*\,")]
|
||||||
|
[InlineData(@"Text@=*\, ")]
|
||||||
|
[InlineData(@"Text@=*braces\,")]
|
||||||
|
[InlineData(@"Text@=*braces\, comma")]
|
||||||
|
public void CanFilterWithEscapedComma(string filter)
|
||||||
|
{
|
||||||
|
var model = new SieveModel
|
||||||
|
{
|
||||||
|
Filters = filter
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _comments);
|
||||||
|
|
||||||
|
Assert.True(result.Count() == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void EqualsDoesntFailWithNonStringTypes()
|
public void EqualsDoesntFailWithNonStringTypes()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "LikeCount==50",
|
Filters = "LikeCount==50",
|
||||||
};
|
};
|
||||||
|
|
||||||
Console.WriteLine(model.GetFiltersParsed()[0].Values);
|
_testOutputHelper.WriteLine(model.GetFiltersParsed()[0].Values.ToString());
|
||||||
Console.WriteLine(model.GetFiltersParsed()[0].Operator);
|
_testOutputHelper.WriteLine(model.GetFiltersParsed()[0].Operator);
|
||||||
Console.WriteLine(model.GetFiltersParsed()[0].OperatorParsed);
|
_testOutputHelper.WriteLine(model.GetFiltersParsed()[0].OperatorParsed.ToString());
|
||||||
|
|
||||||
var result = _processor.Apply(model, _posts);
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
Assert.AreEqual(result.First().Id, 1);
|
Assert.Equal(1, result.First().Id);
|
||||||
Assert.IsTrue(result.Count() == 1);
|
Assert.True(result.Count() == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void CustomFiltersWork()
|
public void CustomFiltersWork()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "Isnew",
|
Filters = "Isnew",
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, _posts);
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
Assert.IsFalse(result.Any(p => p.Id == 0));
|
Assert.False(result.Any(p => p.Id == 0));
|
||||||
Assert.IsTrue(result.Count() == 3);
|
Assert.True(result.Count() == 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void CustomGenericFiltersWork()
|
public void CustomGenericFiltersWork()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "Latest",
|
Filters = "Latest",
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, _comments);
|
var result = _processor.Apply(model, _comments);
|
||||||
|
|
||||||
Assert.IsFalse(result.Any(p => p.Id == 0));
|
Assert.False(result.Any(p => p.Id == 0));
|
||||||
Assert.IsTrue(result.Count() == 2);
|
Assert.True(result.Count() == 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void CustomFiltersWithOperatorsWork()
|
public void CustomFiltersWithOperatorsWork()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "HasInTitle==A",
|
Filters = "HasInTitle==A",
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, _posts);
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
Assert.IsTrue(result.Any(p => p.Id == 0));
|
Assert.True(result.Any(p => p.Id == 0));
|
||||||
Assert.IsTrue(result.Count() == 1);
|
Assert.True(result.Count() == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void CustomFiltersMixedWithUsualWork1()
|
public void CustomFiltersMixedWithUsualWork1()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "Isnew,CategoryId==2",
|
Filters = "Isnew,CategoryId==2",
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, _posts);
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
Assert.IsTrue(result.Any(p => p.Id == 3));
|
Assert.True(result.Any(p => p.Id == 3));
|
||||||
Assert.IsTrue(result.Count() == 1);
|
Assert.True(result.Count() == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void CustomFiltersMixedWithUsualWork2()
|
public void CustomFiltersMixedWithUsualWork2()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "CategoryId==2,Isnew",
|
Filters = "CategoryId==2,Isnew",
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, _posts);
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
Assert.IsTrue(result.Any(p => p.Id == 3));
|
Assert.True(result.Any(p => p.Id == 3));
|
||||||
Assert.IsTrue(result.Count() == 1);
|
Assert.True(result.Count() == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void CustomFiltersOnDifferentSourcesCanShareName()
|
public void CustomFiltersOnDifferentSourcesCanShareName()
|
||||||
{
|
{
|
||||||
var postModel = new SieveModel()
|
var postModel = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "CategoryId==2,Isnew",
|
Filters = "CategoryId==2,Isnew",
|
||||||
};
|
};
|
||||||
|
|
||||||
var postResult = _processor.Apply(postModel, _posts);
|
var postResult = _processor.Apply(postModel, _posts);
|
||||||
|
|
||||||
Assert.IsTrue(postResult.Any(p => p.Id == 3));
|
Assert.True(postResult.Any(p => p.Id == 3));
|
||||||
Assert.AreEqual(1, postResult.Count());
|
Assert.Equal(1, postResult.Count());
|
||||||
|
|
||||||
var commentModel = new SieveModel()
|
var commentModel = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "Isnew",
|
Filters = "Isnew",
|
||||||
};
|
};
|
||||||
|
|
||||||
var commentResult = _processor.Apply(commentModel, _comments);
|
var commentResult = _processor.Apply(commentModel, _comments);
|
||||||
|
|
||||||
Assert.IsTrue(commentResult.Any(c => c.Id == 2));
|
Assert.True(commentResult.Any(c => c.Id == 2));
|
||||||
Assert.AreEqual(2, commentResult.Count());
|
Assert.Equal(2, commentResult.Count());
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void CustomSortsWork()
|
public void CustomSortsWork()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Sorts = "Popularity",
|
Sorts = "Popularity",
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, _posts);
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
Assert.IsFalse(result.First().Id == 0);
|
Assert.False(result.First().Id == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void CustomGenericSortsWork()
|
public void CustomGenericSortsWork()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Sorts = "Oldest",
|
Sorts = "Oldest",
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, _posts);
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
Assert.IsTrue(result.Last().Id == 0);
|
Assert.True(result.Last().Id == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void MethodNotFoundExceptionWork()
|
public void MethodNotFoundExceptionWork()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "does not exist",
|
Filters = "does not exist",
|
||||||
};
|
};
|
||||||
|
|
||||||
Assert.ThrowsException<SieveMethodNotFoundException>(() => _processor.Apply(model, _posts));
|
Assert.Throws<SieveMethodNotFoundException>(() => _processor.Apply(model, _posts));
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void IncompatibleMethodExceptionsWork()
|
public void IncompatibleMethodExceptionsWork()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "TestComment",
|
Filters = "TestComment",
|
||||||
};
|
};
|
||||||
|
|
||||||
Assert.ThrowsException<SieveIncompatibleMethodException>(() => _processor.Apply(model, _posts));
|
Assert.Throws<SieveIncompatibleMethodException>(() => _processor.Apply(model, _posts));
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void OrNameFilteringWorks()
|
public void OrNameFilteringWorks()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "(Title|LikeCount)==3",
|
Filters = "(Title|LikeCount)==3",
|
||||||
};
|
};
|
||||||
@ -348,17 +373,17 @@ namespace SieveUnitTests
|
|||||||
var entry = result.FirstOrDefault();
|
var entry = result.FirstOrDefault();
|
||||||
var resultCount = result.Count();
|
var resultCount = result.Count();
|
||||||
|
|
||||||
Assert.IsNotNull(entry);
|
Assert.NotNull(entry);
|
||||||
Assert.AreEqual(1, resultCount);
|
Assert.Equal(1, resultCount);
|
||||||
Assert.AreEqual(3, entry.Id);
|
Assert.Equal(3, entry.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
[DataTestMethod]
|
[Theory]
|
||||||
[DataRow("CategoryId==1,(CategoryId|LikeCount)==50")]
|
[InlineData("CategoryId==1,(CategoryId|LikeCount)==50")]
|
||||||
[DataRow("(CategoryId|LikeCount)==50,CategoryId==1")]
|
[InlineData("(CategoryId|LikeCount)==50,CategoryId==1")]
|
||||||
public void CombinedAndOrFilterIndependentOfOrder(string filter)
|
public void CombinedAndOrFilterIndependentOfOrder(string filter)
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = filter,
|
Filters = filter,
|
||||||
};
|
};
|
||||||
@ -367,14 +392,14 @@ namespace SieveUnitTests
|
|||||||
var entry = result.FirstOrDefault();
|
var entry = result.FirstOrDefault();
|
||||||
var resultCount = result.Count();
|
var resultCount = result.Count();
|
||||||
|
|
||||||
Assert.IsNotNull(entry);
|
Assert.NotNull(entry);
|
||||||
Assert.AreEqual(1, resultCount);
|
Assert.Equal(1, resultCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void CombinedAndOrWithSpaceFilteringWorks()
|
public void CombinedAndOrWithSpaceFilteringWorks()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "Title==D, (Title|LikeCount)==3",
|
Filters = "Title==D, (Title|LikeCount)==3",
|
||||||
};
|
};
|
||||||
@ -383,97 +408,98 @@ namespace SieveUnitTests
|
|||||||
var entry = result.FirstOrDefault();
|
var entry = result.FirstOrDefault();
|
||||||
var resultCount = result.Count();
|
var resultCount = result.Count();
|
||||||
|
|
||||||
Assert.IsNotNull(entry);
|
Assert.NotNull(entry);
|
||||||
Assert.AreEqual(1, resultCount);
|
Assert.Equal(1, resultCount);
|
||||||
Assert.AreEqual(3, entry.Id);
|
Assert.Equal(3, entry.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void OrValueFilteringWorks()
|
public void OrValueFilteringWorks()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "Title==C|D",
|
Filters = "Title==C|D",
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, _posts);
|
var result = _processor.Apply(model, _posts);
|
||||||
Assert.AreEqual(2, result.Count());
|
Assert.Equal(2, result.Count());
|
||||||
Assert.IsTrue(result.Any(p => p.Id == 2));
|
Assert.True(result.Any(p => p.Id == 2));
|
||||||
Assert.IsTrue(result.Any(p => p.Id == 3));
|
Assert.True(result.Any(p => p.Id == 3));
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void OrValueFilteringWorks2()
|
public void OrValueFilteringWorks2()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "Text@=(|)",
|
Filters = "Text@=(|)",
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, _comments);
|
var result = _processor.Apply(model, _comments);
|
||||||
Assert.AreEqual(1, result.Count());
|
Assert.Equal(1, result.Count());
|
||||||
Assert.AreEqual(2, result.FirstOrDefault().Id);
|
Assert.Equal(2, result.FirstOrDefault()?.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void NestedFilteringWorks()
|
public void NestedFilteringWorks()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "TopComment.Text!@=A",
|
Filters = "TopComment.Text!@=A",
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, _posts);
|
var result = _processor.Apply(model, _posts);
|
||||||
Assert.AreEqual(3, result.Count());
|
Assert.Equal(3, result.Count());
|
||||||
var posts = result.ToList();
|
var posts = result.ToList();
|
||||||
Assert.IsTrue(posts[0].TopComment.Text.Contains("B"));
|
Assert.Contains("B", posts[0].TopComment.Text);
|
||||||
Assert.IsTrue(posts[1].TopComment.Text.Contains("C"));
|
Assert.Contains("C", posts[1].TopComment.Text);
|
||||||
Assert.IsTrue(posts[2].TopComment.Text.Contains("D"));
|
Assert.Contains("D", posts[2].TopComment.Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void NestedSortingWorks()
|
public void NestedSortingWorks()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Sorts = "TopComment.Id",
|
Sorts = "TopComment.Id",
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, _posts);
|
var result = _processor.Apply(model, _posts);
|
||||||
Assert.AreEqual(4, result.Count());
|
Assert.Equal(4, result.Count());
|
||||||
var posts = result.ToList();
|
var posts = result.ToList();
|
||||||
Assert.AreEqual(posts[0].Id, 0);
|
Assert.Equal(0, posts[0].Id);
|
||||||
Assert.AreEqual(posts[1].Id, 3);
|
Assert.Equal(3, posts[1].Id);
|
||||||
Assert.AreEqual(posts[2].Id, 2);
|
Assert.Equal(2, posts[2].Id);
|
||||||
Assert.AreEqual(posts[3].Id, 1);
|
Assert.Equal(1, posts[3].Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void NestedFilteringWithIdenticTypesWorks()
|
public void NestedFilteringWithIdenticTypesWorks()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "(topc|featc)@=*2",
|
Filters = "(topc|featc)@=*2",
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, _posts);
|
var result = _processor.Apply(model, _posts);
|
||||||
Assert.AreEqual(4, result.Count());
|
Assert.Equal(4, result.Count());
|
||||||
|
|
||||||
model = new SieveModel()
|
model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "(topc|featc)@=*B",
|
Filters = "(topc|featc)@=*B",
|
||||||
};
|
};
|
||||||
|
|
||||||
result = _processor.Apply(model, _posts);
|
result = _processor.Apply(model, _posts);
|
||||||
Assert.AreEqual(1, result.Count());
|
Assert.Equal(1, result.Count());
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void FilteringNullsWorks()
|
public void FilteringNullsWorks()
|
||||||
{
|
{
|
||||||
var posts = new List<Post>
|
var posts = new List<Post>
|
||||||
{
|
{
|
||||||
new Post() {
|
new Post
|
||||||
|
{
|
||||||
Id = 1,
|
Id = 1,
|
||||||
Title = null,
|
Title = null,
|
||||||
LikeCount = 0,
|
LikeCount = 0,
|
||||||
@ -484,21 +510,22 @@ namespace SieveUnitTests
|
|||||||
},
|
},
|
||||||
}.AsQueryable();
|
}.AsQueryable();
|
||||||
|
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "FeaturedComment.Text!@=Some value",
|
Filters = "FeaturedComment.Text!@=Some value",
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, posts);
|
var result = _processor.Apply(model, posts);
|
||||||
Assert.AreEqual(0, result.Count());
|
Assert.Equal(0, result.Count());
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void SortingNullsWorks()
|
public void SortingNullsWorks()
|
||||||
{
|
{
|
||||||
var posts = new List<Post>
|
var posts = new List<Post>
|
||||||
{
|
{
|
||||||
new Post() {
|
new Post
|
||||||
|
{
|
||||||
Id = 1,
|
Id = 1,
|
||||||
Title = null,
|
Title = null,
|
||||||
LikeCount = 0,
|
LikeCount = 0,
|
||||||
@ -507,7 +534,8 @@ namespace SieveUnitTests
|
|||||||
TopComment = new Comment { Id = 1 },
|
TopComment = new Comment { Id = 1 },
|
||||||
FeaturedComment = null
|
FeaturedComment = null
|
||||||
},
|
},
|
||||||
new Post() {
|
new Post
|
||||||
|
{
|
||||||
Id = 2,
|
Id = 2,
|
||||||
Title = null,
|
Title = null,
|
||||||
LikeCount = 0,
|
LikeCount = 0,
|
||||||
@ -518,24 +546,25 @@ namespace SieveUnitTests
|
|||||||
},
|
},
|
||||||
}.AsQueryable();
|
}.AsQueryable();
|
||||||
|
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Sorts = "TopComment.Id",
|
Sorts = "TopComment.Id",
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, posts);
|
var result = _processor.Apply(model, posts);
|
||||||
Assert.AreEqual(2, result.Count());
|
Assert.Equal(2, result.Count());
|
||||||
var sortedPosts = result.ToList();
|
var sortedPosts = result.ToList();
|
||||||
Assert.AreEqual(sortedPosts[0].Id, 2);
|
Assert.Equal(2, sortedPosts[0].Id);
|
||||||
Assert.AreEqual(sortedPosts[1].Id, 1);
|
Assert.Equal(1, sortedPosts[1].Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void FilteringOnNullWorks()
|
public void FilteringOnNullWorks()
|
||||||
{
|
{
|
||||||
var posts = new List<Post>
|
var posts = new List<Post>
|
||||||
{
|
{
|
||||||
new Post() {
|
new Post
|
||||||
|
{
|
||||||
Id = 1,
|
Id = 1,
|
||||||
Title = null,
|
Title = null,
|
||||||
LikeCount = 0,
|
LikeCount = 0,
|
||||||
@ -544,7 +573,8 @@ namespace SieveUnitTests
|
|||||||
TopComment = null,
|
TopComment = null,
|
||||||
FeaturedComment = null
|
FeaturedComment = null
|
||||||
},
|
},
|
||||||
new Post() {
|
new Post
|
||||||
|
{
|
||||||
Id = 2,
|
Id = 2,
|
||||||
Title = null,
|
Title = null,
|
||||||
LikeCount = 0,
|
LikeCount = 0,
|
||||||
@ -555,33 +585,33 @@ namespace SieveUnitTests
|
|||||||
},
|
},
|
||||||
}.AsQueryable();
|
}.AsQueryable();
|
||||||
|
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "FeaturedComment.Text==null",
|
Filters = "FeaturedComment.Text==null",
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, posts);
|
var result = _processor.Apply(model, posts);
|
||||||
Assert.AreEqual(1, result.Count());
|
Assert.Equal(1, result.Count());
|
||||||
var filteredPosts = result.ToList();
|
var filteredPosts = result.ToList();
|
||||||
Assert.AreEqual(filteredPosts[0].Id, 2);
|
Assert.Equal(2, filteredPosts[0].Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void BaseDefinedPropertyMappingSortingWorks_WithCustomName()
|
public void BaseDefinedPropertyMappingSortingWorks_WithCustomName()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Sorts = "-CreateDate"
|
Sorts = "-CreateDate"
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, _posts);
|
var result = _processor.Apply(model, _posts);
|
||||||
Assert.AreEqual(4, result.Count());
|
Assert.Equal(4, result.Count());
|
||||||
|
|
||||||
var posts = result.ToList();
|
var posts = result.ToList();
|
||||||
Assert.AreEqual(posts[0].Id, 3);
|
Assert.Equal(3,posts[0].Id);
|
||||||
Assert.AreEqual(posts[1].Id, 2);
|
Assert.Equal(2,posts[1].Id);
|
||||||
Assert.AreEqual(posts[2].Id, 1);
|
Assert.Equal(1,posts[2].Id);
|
||||||
Assert.AreEqual(posts[3].Id, 0);
|
Assert.Equal(0,posts[3].Id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,33 +1,35 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
|
||||||
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.Abstractions.Entity;
|
||||||
using SieveUnitTests.Entities;
|
using SieveUnitTests.Entities;
|
||||||
using SieveUnitTests.Services;
|
using SieveUnitTests.Services;
|
||||||
|
using Xunit;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
namespace SieveUnitTests
|
namespace SieveUnitTests
|
||||||
{
|
{
|
||||||
[TestClass]
|
|
||||||
public class GeneralWithInterfaces
|
public class GeneralWithInterfaces
|
||||||
{
|
{
|
||||||
|
private readonly ITestOutputHelper _testOutputHelper;
|
||||||
private readonly SieveProcessor _processor;
|
private readonly SieveProcessor _processor;
|
||||||
private readonly IQueryable<IPost> _posts;
|
private readonly IQueryable<IPost> _posts;
|
||||||
private readonly IQueryable<Comment> _comments;
|
private readonly IQueryable<Comment> _comments;
|
||||||
|
|
||||||
public GeneralWithInterfaces()
|
public GeneralWithInterfaces(ITestOutputHelper testOutputHelper)
|
||||||
{
|
{
|
||||||
|
_testOutputHelper = testOutputHelper;
|
||||||
_processor = new ApplicationSieveProcessor(new SieveOptionsAccessor(),
|
_processor = new ApplicationSieveProcessor(new SieveOptionsAccessor(),
|
||||||
new SieveCustomSortMethods(),
|
new SieveCustomSortMethods(),
|
||||||
new SieveCustomFilterMethods());
|
new SieveCustomFilterMethods());
|
||||||
|
|
||||||
_posts = new List<IPost>
|
_posts = new List<IPost>
|
||||||
{
|
{
|
||||||
new Post() {
|
new Post
|
||||||
|
{
|
||||||
Id = 0,
|
Id = 0,
|
||||||
Title = "A",
|
Title = "A",
|
||||||
LikeCount = 100,
|
LikeCount = 100,
|
||||||
@ -36,7 +38,8 @@ namespace SieveUnitTests
|
|||||||
TopComment = new Comment { Id = 0, Text = "A1" },
|
TopComment = new Comment { Id = 0, Text = "A1" },
|
||||||
FeaturedComment = new Comment { Id = 4, Text = "A2" }
|
FeaturedComment = new Comment { Id = 4, Text = "A2" }
|
||||||
},
|
},
|
||||||
new Post() {
|
new Post
|
||||||
|
{
|
||||||
Id = 1,
|
Id = 1,
|
||||||
Title = "B",
|
Title = "B",
|
||||||
LikeCount = 50,
|
LikeCount = 50,
|
||||||
@ -45,7 +48,8 @@ namespace SieveUnitTests
|
|||||||
TopComment = new Comment { Id = 3, Text = "B1" },
|
TopComment = new Comment { Id = 3, Text = "B1" },
|
||||||
FeaturedComment = new Comment { Id = 5, Text = "B2" }
|
FeaturedComment = new Comment { Id = 5, Text = "B2" }
|
||||||
},
|
},
|
||||||
new Post() {
|
new Post
|
||||||
|
{
|
||||||
Id = 2,
|
Id = 2,
|
||||||
Title = "C",
|
Title = "C",
|
||||||
LikeCount = 0,
|
LikeCount = 0,
|
||||||
@ -53,7 +57,8 @@ namespace SieveUnitTests
|
|||||||
TopComment = new Comment { Id = 2, Text = "C1" },
|
TopComment = new Comment { Id = 2, Text = "C1" },
|
||||||
FeaturedComment = new Comment { Id = 6, Text = "C2" }
|
FeaturedComment = new Comment { Id = 6, Text = "C2" }
|
||||||
},
|
},
|
||||||
new Post() {
|
new Post
|
||||||
|
{
|
||||||
Id = 3,
|
Id = 3,
|
||||||
Title = "D",
|
Title = "D",
|
||||||
LikeCount = 3,
|
LikeCount = 3,
|
||||||
@ -66,17 +71,20 @@ namespace SieveUnitTests
|
|||||||
|
|
||||||
_comments = new List<Comment>
|
_comments = new List<Comment>
|
||||||
{
|
{
|
||||||
new Comment() {
|
new Comment
|
||||||
|
{
|
||||||
Id = 0,
|
Id = 0,
|
||||||
DateCreated = DateTimeOffset.UtcNow.AddDays(-20),
|
DateCreated = DateTimeOffset.UtcNow.AddDays(-20),
|
||||||
Text = "This is an old comment."
|
Text = "This is an old comment."
|
||||||
},
|
},
|
||||||
new Comment() {
|
new Comment
|
||||||
|
{
|
||||||
Id = 1,
|
Id = 1,
|
||||||
DateCreated = DateTimeOffset.UtcNow.AddDays(-1),
|
DateCreated = DateTimeOffset.UtcNow.AddDays(-1),
|
||||||
Text = "This is a fairly new comment."
|
Text = "This is a fairly new comment."
|
||||||
},
|
},
|
||||||
new Comment() {
|
new Comment
|
||||||
|
{
|
||||||
Id = 2,
|
Id = 2,
|
||||||
DateCreated = DateTimeOffset.UtcNow,
|
DateCreated = DateTimeOffset.UtcNow,
|
||||||
Text = "This is a brand new comment. (Text in braces)"
|
Text = "This is a brand new comment. (Text in braces)"
|
||||||
@ -84,263 +92,263 @@ namespace SieveUnitTests
|
|||||||
}.AsQueryable();
|
}.AsQueryable();
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void ContainsCanBeCaseInsensitive()
|
public void ContainsCanBeCaseInsensitive()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "Title@=*a"
|
Filters = "Title@=*a"
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, _posts);
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
Assert.AreEqual(result.First().Id, 0);
|
Assert.Equal(0, result.First().Id);
|
||||||
Assert.IsTrue(result.Count() == 1);
|
Assert.True(result.Count() == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void NotEqualsCanBeCaseInsensitive()
|
public void NotEqualsCanBeCaseInsensitive()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "Title!=*a"
|
Filters = "Title!=*a"
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, _posts);
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
Assert.AreEqual(result.First().Id, 1);
|
Assert.Equal(1, result.First().Id);
|
||||||
Assert.IsTrue(result.Count() == 3);
|
Assert.True(result.Count() == 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void ContainsIsCaseSensitive()
|
public void ContainsIsCaseSensitive()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "Title@=a",
|
Filters = "Title@=a",
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, _posts);
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
Assert.IsTrue(result.Count() == 0);
|
Assert.True(!result.Any());
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void NotContainsWorks()
|
public void NotContainsWorks()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "Title!@=D",
|
Filters = "Title!@=D",
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, _posts);
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
Assert.IsTrue(result.Count() == 3);
|
Assert.True(result.Count() == 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void CanFilterBools()
|
public void CanFilterBools()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "IsDraft==false"
|
Filters = "IsDraft==false"
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, _posts);
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
Assert.IsTrue(result.Count() == 2);
|
Assert.True(result.Count() == 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void CanSortBools()
|
public void CanSortBools()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Sorts = "-IsDraft"
|
Sorts = "-IsDraft"
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, _posts);
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
Assert.AreEqual(result.First().Id, 0);
|
Assert.Equal(0, result.First().Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void CanFilterNullableInts()
|
public void CanFilterNullableInts()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "CategoryId==1"
|
Filters = "CategoryId==1"
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, _posts);
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
Assert.IsTrue(result.Count() == 2);
|
Assert.True(result.Count() == 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void EqualsDoesntFailWithNonStringTypes()
|
public void EqualsDoesntFailWithNonStringTypes()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "LikeCount==50",
|
Filters = "LikeCount==50",
|
||||||
};
|
};
|
||||||
|
|
||||||
Console.WriteLine(model.GetFiltersParsed()[0].Values);
|
_testOutputHelper.WriteLine(model.GetFiltersParsed()[0].Values.ToString());
|
||||||
Console.WriteLine(model.GetFiltersParsed()[0].Operator);
|
_testOutputHelper.WriteLine(model.GetFiltersParsed()[0].Operator);
|
||||||
Console.WriteLine(model.GetFiltersParsed()[0].OperatorParsed);
|
_testOutputHelper.WriteLine(model.GetFiltersParsed()[0].OperatorParsed.ToString());
|
||||||
|
|
||||||
var result = _processor.Apply(model, _posts);
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
Assert.AreEqual(result.First().Id, 1);
|
Assert.Equal(1, result.First().Id);
|
||||||
Assert.IsTrue(result.Count() == 1);
|
Assert.True(result.Count() == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void CustomFiltersWork()
|
public void CustomFiltersWork()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "Isnew",
|
Filters = "Isnew",
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, _posts);
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
Assert.IsFalse(result.Any(p => p.Id == 0));
|
Assert.False(result.Any(p => p.Id == 0));
|
||||||
Assert.IsTrue(result.Count() == 3);
|
Assert.True(result.Count() == 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void CustomGenericFiltersWork()
|
public void CustomGenericFiltersWork()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "Latest",
|
Filters = "Latest",
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, _comments);
|
var result = _processor.Apply(model, _comments);
|
||||||
|
|
||||||
Assert.IsFalse(result.Any(p => p.Id == 0));
|
Assert.False(result.Any(p => p.Id == 0));
|
||||||
Assert.IsTrue(result.Count() == 2);
|
Assert.True(result.Count() == 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void CustomFiltersWithOperatorsWork()
|
public void CustomFiltersWithOperatorsWork()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "HasInTitle==A",
|
Filters = "HasInTitle==A",
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, _posts);
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
Assert.IsTrue(result.Any(p => p.Id == 0));
|
Assert.True(result.Any(p => p.Id == 0));
|
||||||
Assert.IsTrue(result.Count() == 1);
|
Assert.True(result.Count() == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void CustomFiltersMixedWithUsualWork1()
|
public void CustomFiltersMixedWithUsualWork1()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "Isnew,CategoryId==2",
|
Filters = "Isnew,CategoryId==2",
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, _posts);
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
Assert.IsTrue(result.Any(p => p.Id == 3));
|
Assert.True(result.Any(p => p.Id == 3));
|
||||||
Assert.IsTrue(result.Count() == 1);
|
Assert.True(result.Count() == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void CustomFiltersMixedWithUsualWork2()
|
public void CustomFiltersMixedWithUsualWork2()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "CategoryId==2,Isnew",
|
Filters = "CategoryId==2,Isnew",
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, _posts);
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
Assert.IsTrue(result.Any(p => p.Id == 3));
|
Assert.True(result.Any(p => p.Id == 3));
|
||||||
Assert.IsTrue(result.Count() == 1);
|
Assert.True(result.Count() == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void CustomFiltersOnDifferentSourcesCanShareName()
|
public void CustomFiltersOnDifferentSourcesCanShareName()
|
||||||
{
|
{
|
||||||
var postModel = new SieveModel()
|
var postModel = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "CategoryId==2,Isnew",
|
Filters = "CategoryId==2,Isnew",
|
||||||
};
|
};
|
||||||
|
|
||||||
var postResult = _processor.Apply(postModel, _posts);
|
var postResult = _processor.Apply(postModel, _posts);
|
||||||
|
|
||||||
Assert.IsTrue(postResult.Any(p => p.Id == 3));
|
Assert.True(postResult.Any(p => p.Id == 3));
|
||||||
Assert.AreEqual(1, postResult.Count());
|
Assert.Equal(1, postResult.Count());
|
||||||
|
|
||||||
var commentModel = new SieveModel()
|
var commentModel = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "Isnew",
|
Filters = "Isnew",
|
||||||
};
|
};
|
||||||
|
|
||||||
var commentResult = _processor.Apply(commentModel, _comments);
|
var commentResult = _processor.Apply(commentModel, _comments);
|
||||||
|
|
||||||
Assert.IsTrue(commentResult.Any(c => c.Id == 2));
|
Assert.True(commentResult.Any(c => c.Id == 2));
|
||||||
Assert.AreEqual(2, commentResult.Count());
|
Assert.Equal(2, commentResult.Count());
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void CustomSortsWork()
|
public void CustomSortsWork()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Sorts = "Popularity",
|
Sorts = "Popularity",
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, _posts);
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
Assert.IsFalse(result.First().Id == 0);
|
Assert.False(result.First().Id == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void CustomGenericSortsWork()
|
public void CustomGenericSortsWork()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Sorts = "Oldest",
|
Sorts = "Oldest",
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, _posts);
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
Assert.IsTrue(result.Last().Id == 0);
|
Assert.True(result.Last().Id == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void MethodNotFoundExceptionWork()
|
public void MethodNotFoundExceptionWork()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "does not exist",
|
Filters = "does not exist",
|
||||||
};
|
};
|
||||||
|
|
||||||
Assert.ThrowsException<SieveMethodNotFoundException>(() => _processor.Apply(model, _posts));
|
Assert.Throws<SieveMethodNotFoundException>(() => _processor.Apply(model, _posts));
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void IncompatibleMethodExceptionsWork()
|
public void IncompatibleMethodExceptionsWork()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "TestComment",
|
Filters = "TestComment",
|
||||||
};
|
};
|
||||||
|
|
||||||
Assert.ThrowsException<SieveIncompatibleMethodException>(() => _processor.Apply(model, _posts));
|
Assert.Throws<SieveIncompatibleMethodException>(() => _processor.Apply(model, _posts));
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void OrNameFilteringWorks()
|
public void OrNameFilteringWorks()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "(Title|LikeCount)==3",
|
Filters = "(Title|LikeCount)==3",
|
||||||
};
|
};
|
||||||
@ -349,17 +357,17 @@ namespace SieveUnitTests
|
|||||||
var entry = result.FirstOrDefault();
|
var entry = result.FirstOrDefault();
|
||||||
var resultCount = result.Count();
|
var resultCount = result.Count();
|
||||||
|
|
||||||
Assert.IsNotNull(entry);
|
Assert.NotNull(entry);
|
||||||
Assert.AreEqual(1, resultCount);
|
Assert.Equal(1, resultCount);
|
||||||
Assert.AreEqual(3, entry.Id);
|
Assert.Equal(3, entry.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
[DataTestMethod]
|
[Theory]
|
||||||
[DataRow("CategoryId==1,(CategoryId|LikeCount)==50")]
|
[InlineData("CategoryId==1,(CategoryId|LikeCount)==50")]
|
||||||
[DataRow("(CategoryId|LikeCount)==50,CategoryId==1")]
|
[InlineData("(CategoryId|LikeCount)==50,CategoryId==1")]
|
||||||
public void CombinedAndOrFilterIndependentOfOrder(string filter)
|
public void CombinedAndOrFilterIndependentOfOrder(string filter)
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = filter,
|
Filters = filter,
|
||||||
};
|
};
|
||||||
@ -368,14 +376,14 @@ namespace SieveUnitTests
|
|||||||
var entry = result.FirstOrDefault();
|
var entry = result.FirstOrDefault();
|
||||||
var resultCount = result.Count();
|
var resultCount = result.Count();
|
||||||
|
|
||||||
Assert.IsNotNull(entry);
|
Assert.NotNull(entry);
|
||||||
Assert.AreEqual(1, resultCount);
|
Assert.Equal(1, resultCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void CombinedAndOrWithSpaceFilteringWorks()
|
public void CombinedAndOrWithSpaceFilteringWorks()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "Title==D, (Title|LikeCount)==3",
|
Filters = "Title==D, (Title|LikeCount)==3",
|
||||||
};
|
};
|
||||||
@ -384,97 +392,98 @@ namespace SieveUnitTests
|
|||||||
var entry = result.FirstOrDefault();
|
var entry = result.FirstOrDefault();
|
||||||
var resultCount = result.Count();
|
var resultCount = result.Count();
|
||||||
|
|
||||||
Assert.IsNotNull(entry);
|
Assert.NotNull(entry);
|
||||||
Assert.AreEqual(1, resultCount);
|
Assert.Equal(1, resultCount);
|
||||||
Assert.AreEqual(3, entry.Id);
|
Assert.Equal(3, entry.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void OrValueFilteringWorks()
|
public void OrValueFilteringWorks()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "Title==C|D",
|
Filters = "Title==C|D",
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, _posts);
|
var result = _processor.Apply(model, _posts);
|
||||||
Assert.AreEqual(2, result.Count());
|
Assert.Equal(2, result.Count());
|
||||||
Assert.IsTrue(result.Any(p => p.Id == 2));
|
Assert.True(result.Any(p => p.Id == 2));
|
||||||
Assert.IsTrue(result.Any(p => p.Id == 3));
|
Assert.True(result.Any(p => p.Id == 3));
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void OrValueFilteringWorks2()
|
public void OrValueFilteringWorks2()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "Text@=(|)",
|
Filters = "Text@=(|)",
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, _comments);
|
var result = _processor.Apply(model, _comments);
|
||||||
Assert.AreEqual(1, result.Count());
|
Assert.Equal(1, result.Count());
|
||||||
Assert.AreEqual(2, result.FirstOrDefault().Id);
|
Assert.Equal(2, result.FirstOrDefault()?.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void NestedFilteringWorks()
|
public void NestedFilteringWorks()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "TopComment.Text!@=A",
|
Filters = "TopComment.Text!@=A",
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, _posts);
|
var result = _processor.Apply(model, _posts);
|
||||||
Assert.AreEqual(3, result.Count());
|
Assert.Equal(3, result.Count());
|
||||||
var posts = result.ToList();
|
var posts = result.ToList();
|
||||||
Assert.IsTrue(posts[0].TopComment.Text.Contains("B"));
|
Assert.Contains("B", posts[0].TopComment.Text);
|
||||||
Assert.IsTrue(posts[1].TopComment.Text.Contains("C"));
|
Assert.Contains("C", posts[1].TopComment.Text);
|
||||||
Assert.IsTrue(posts[2].TopComment.Text.Contains("D"));
|
Assert.Contains("D", posts[2].TopComment.Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void NestedSortingWorks()
|
public void NestedSortingWorks()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Sorts = "TopComment.Id",
|
Sorts = "TopComment.Id",
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, _posts);
|
var result = _processor.Apply(model, _posts);
|
||||||
Assert.AreEqual(4, result.Count());
|
Assert.Equal(4, result.Count());
|
||||||
var posts = result.ToList();
|
var posts = result.ToList();
|
||||||
Assert.AreEqual(posts[0].Id, 0);
|
Assert.Equal(0, posts[0].Id);
|
||||||
Assert.AreEqual(posts[1].Id, 3);
|
Assert.Equal(3, posts[1].Id);
|
||||||
Assert.AreEqual(posts[2].Id, 2);
|
Assert.Equal(2, posts[2].Id);
|
||||||
Assert.AreEqual(posts[3].Id, 1);
|
Assert.Equal(1, posts[3].Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void NestedFilteringWithIdenticTypesWorks()
|
public void NestedFilteringWithIdenticTypesWorks()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "(topc|featc)@=*2",
|
Filters = "(topc|featc)@=*2",
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, _posts);
|
var result = _processor.Apply(model, _posts);
|
||||||
Assert.AreEqual(4, result.Count());
|
Assert.Equal(4, result.Count());
|
||||||
|
|
||||||
model = new SieveModel()
|
model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "(topc|featc)@=*B",
|
Filters = "(topc|featc)@=*B",
|
||||||
};
|
};
|
||||||
|
|
||||||
result = _processor.Apply(model, _posts);
|
result = _processor.Apply(model, _posts);
|
||||||
Assert.AreEqual(1, result.Count());
|
Assert.Equal(1, result.Count());
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void FilteringNullsWorks()
|
public void FilteringNullsWorks()
|
||||||
{
|
{
|
||||||
var posts = new List<Post>
|
var posts = new List<Post>
|
||||||
{
|
{
|
||||||
new Post() {
|
new Post
|
||||||
|
{
|
||||||
Id = 1,
|
Id = 1,
|
||||||
Title = null,
|
Title = null,
|
||||||
LikeCount = 0,
|
LikeCount = 0,
|
||||||
@ -485,21 +494,22 @@ namespace SieveUnitTests
|
|||||||
},
|
},
|
||||||
}.AsQueryable();
|
}.AsQueryable();
|
||||||
|
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "FeaturedComment.Text!@=Some value",
|
Filters = "FeaturedComment.Text!@=Some value",
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, posts);
|
var result = _processor.Apply(model, posts);
|
||||||
Assert.AreEqual(0, result.Count());
|
Assert.Equal(0, result.Count());
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void SortingNullsWorks()
|
public void SortingNullsWorks()
|
||||||
{
|
{
|
||||||
var posts = new List<Post>
|
var posts = new List<Post>
|
||||||
{
|
{
|
||||||
new Post() {
|
new Post
|
||||||
|
{
|
||||||
Id = 1,
|
Id = 1,
|
||||||
Title = null,
|
Title = null,
|
||||||
LikeCount = 0,
|
LikeCount = 0,
|
||||||
@ -508,7 +518,8 @@ namespace SieveUnitTests
|
|||||||
TopComment = new Comment { Id = 1 },
|
TopComment = new Comment { Id = 1 },
|
||||||
FeaturedComment = null
|
FeaturedComment = null
|
||||||
},
|
},
|
||||||
new Post() {
|
new Post
|
||||||
|
{
|
||||||
Id = 2,
|
Id = 2,
|
||||||
Title = null,
|
Title = null,
|
||||||
LikeCount = 0,
|
LikeCount = 0,
|
||||||
@ -519,24 +530,25 @@ namespace SieveUnitTests
|
|||||||
},
|
},
|
||||||
}.AsQueryable();
|
}.AsQueryable();
|
||||||
|
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Sorts = "TopComment.Id",
|
Sorts = "TopComment.Id",
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, posts);
|
var result = _processor.Apply(model, posts);
|
||||||
Assert.AreEqual(2, result.Count());
|
Assert.Equal(2, result.Count());
|
||||||
var sortedPosts = result.ToList();
|
var sortedPosts = result.ToList();
|
||||||
Assert.AreEqual(sortedPosts[0].Id, 2);
|
Assert.Equal(2, sortedPosts[0].Id);
|
||||||
Assert.AreEqual(sortedPosts[1].Id, 1);
|
Assert.Equal(1, sortedPosts[1].Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void FilteringOnNullWorks()
|
public void FilteringOnNullWorks()
|
||||||
{
|
{
|
||||||
var posts = new List<Post>
|
var posts = new List<Post>
|
||||||
{
|
{
|
||||||
new Post() {
|
new Post
|
||||||
|
{
|
||||||
Id = 1,
|
Id = 1,
|
||||||
Title = null,
|
Title = null,
|
||||||
LikeCount = 0,
|
LikeCount = 0,
|
||||||
@ -545,7 +557,8 @@ namespace SieveUnitTests
|
|||||||
TopComment = null,
|
TopComment = null,
|
||||||
FeaturedComment = null
|
FeaturedComment = null
|
||||||
},
|
},
|
||||||
new Post() {
|
new Post
|
||||||
|
{
|
||||||
Id = 2,
|
Id = 2,
|
||||||
Title = null,
|
Title = null,
|
||||||
LikeCount = 0,
|
LikeCount = 0,
|
||||||
@ -556,33 +569,33 @@ namespace SieveUnitTests
|
|||||||
},
|
},
|
||||||
}.AsQueryable();
|
}.AsQueryable();
|
||||||
|
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "FeaturedComment.Text==null",
|
Filters = "FeaturedComment.Text==null",
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, posts);
|
var result = _processor.Apply(model, posts);
|
||||||
Assert.AreEqual(1, result.Count());
|
Assert.Equal(1, result.Count());
|
||||||
var filteredPosts = result.ToList();
|
var filteredPosts = result.ToList();
|
||||||
Assert.AreEqual(filteredPosts[0].Id, 2);
|
Assert.Equal(2, filteredPosts[0].Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void BaseDefinedPropertyMappingSortingWorks_WithCustomName()
|
public void BaseDefinedPropertyMappingSortingWorks_WithCustomName()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Sorts = "-CreateDate"
|
Sorts = "-CreateDate"
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, _posts);
|
var result = _processor.Apply(model, _posts);
|
||||||
Assert.AreEqual(4, result.Count());
|
Assert.Equal(4, result.Count());
|
||||||
|
|
||||||
var posts = result.ToList();
|
var posts = result.ToList();
|
||||||
Assert.AreEqual(posts[0].Id, 3);
|
Assert.Equal(3,posts[0].Id);
|
||||||
Assert.AreEqual(posts[1].Id, 2);
|
Assert.Equal(2,posts[1].Id);
|
||||||
Assert.AreEqual(posts[2].Id, 1);
|
Assert.Equal(1,posts[2].Id);
|
||||||
Assert.AreEqual(posts[3].Id, 0);
|
Assert.Equal(0,posts[3].Id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
||||||
using Sieve.Exceptions;
|
using Sieve.Exceptions;
|
||||||
using Sieve.Models;
|
using Sieve.Models;
|
||||||
using SieveUnitTests.Entities;
|
using SieveUnitTests.Entities;
|
||||||
using SieveUnitTests.Services;
|
using SieveUnitTests.Services;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
namespace SieveUnitTests
|
namespace SieveUnitTests
|
||||||
{
|
{
|
||||||
[TestClass]
|
|
||||||
public class Mapper
|
public class Mapper
|
||||||
{
|
{
|
||||||
private readonly ApplicationSieveProcessor _processor;
|
private readonly ApplicationSieveProcessor _processor;
|
||||||
@ -22,19 +21,22 @@ namespace SieveUnitTests
|
|||||||
|
|
||||||
_posts = new List<Post>
|
_posts = new List<Post>
|
||||||
{
|
{
|
||||||
new Post() {
|
new Post
|
||||||
|
{
|
||||||
Id = 1,
|
Id = 1,
|
||||||
ThisHasNoAttributeButIsAccessible = "A",
|
ThisHasNoAttributeButIsAccessible = "A",
|
||||||
ThisHasNoAttribute = "A",
|
ThisHasNoAttribute = "A",
|
||||||
OnlySortableViaFluentApi = 100
|
OnlySortableViaFluentApi = 100
|
||||||
},
|
},
|
||||||
new Post() {
|
new Post
|
||||||
|
{
|
||||||
Id = 2,
|
Id = 2,
|
||||||
ThisHasNoAttributeButIsAccessible = "B",
|
ThisHasNoAttributeButIsAccessible = "B",
|
||||||
ThisHasNoAttribute = "B",
|
ThisHasNoAttribute = "B",
|
||||||
OnlySortableViaFluentApi = 50
|
OnlySortableViaFluentApi = 50
|
||||||
},
|
},
|
||||||
new Post() {
|
new Post
|
||||||
|
{
|
||||||
Id = 3,
|
Id = 3,
|
||||||
ThisHasNoAttributeButIsAccessible = "C",
|
ThisHasNoAttributeButIsAccessible = "C",
|
||||||
ThisHasNoAttribute = "C",
|
ThisHasNoAttribute = "C",
|
||||||
@ -43,25 +45,25 @@ namespace SieveUnitTests
|
|||||||
}.AsQueryable();
|
}.AsQueryable();
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void MapperWorks()
|
public void MapperWorks()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "shortname@=A",
|
Filters = "shortname@=A",
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _processor.Apply(model, _posts);
|
var result = _processor.Apply(model, _posts);
|
||||||
|
|
||||||
Assert.AreEqual(result.First().ThisHasNoAttributeButIsAccessible, "A");
|
Assert.Equal("A", result.First().ThisHasNoAttributeButIsAccessible);
|
||||||
|
|
||||||
Assert.IsTrue(result.Count() == 1);
|
Assert.True(result.Count() == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void MapperSortOnlyWorks()
|
public void MapperSortOnlyWorks()
|
||||||
{
|
{
|
||||||
var model = new SieveModel()
|
var model = new SieveModel
|
||||||
{
|
{
|
||||||
Filters = "OnlySortableViaFluentApi@=50",
|
Filters = "OnlySortableViaFluentApi@=50",
|
||||||
Sorts = "OnlySortableViaFluentApi"
|
Sorts = "OnlySortableViaFluentApi"
|
||||||
@ -69,17 +71,11 @@ namespace SieveUnitTests
|
|||||||
|
|
||||||
var result = _processor.Apply(model, _posts, applyFiltering: false, applyPagination: false);
|
var result = _processor.Apply(model, _posts, applyFiltering: false, applyPagination: false);
|
||||||
|
|
||||||
Assert.ThrowsException<SieveMethodNotFoundException>(() => _processor.Apply(model, _posts));
|
Assert.Throws<SieveMethodNotFoundException>(() => _processor.Apply(model, _posts));
|
||||||
|
|
||||||
Assert.AreEqual(result.First().Id, 3);
|
Assert.Equal(3, result.First().Id);
|
||||||
|
|
||||||
Assert.IsTrue(result.Count() == 3);
|
Assert.True(result.Count() == 3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
//Sorts = "LikeCount",
|
|
||||||
//Page = 1,
|
|
||||||
//PageSize = 10
|
|
||||||
//
|
|
@ -1,15 +1,22 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
|
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
||||||
<PackageReference Include="MSTest.TestAdapter" Version="1.2.0" />
|
<PackageReference Include="Microsoft.Extensions.Options" Version="2.0.0" />
|
||||||
<PackageReference Include="MSTest.TestFramework" Version="1.2.0" />
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="coverlet.collector" Version="1.3.0">
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
148
SieveUnitTests/StringFilterNullTests.cs
Normal file
148
SieveUnitTests/StringFilterNullTests.cs
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Sieve.Models;
|
||||||
|
using Sieve.Services;
|
||||||
|
using SieveUnitTests.Entities;
|
||||||
|
using SieveUnitTests.Services;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace SieveUnitTests
|
||||||
|
{
|
||||||
|
public class StringFilterNullTests
|
||||||
|
{
|
||||||
|
private readonly IQueryable<Comment> _comments;
|
||||||
|
private readonly SieveProcessor _processor;
|
||||||
|
|
||||||
|
public StringFilterNullTests()
|
||||||
|
{
|
||||||
|
_processor = new SieveProcessor(new SieveOptionsAccessor());
|
||||||
|
|
||||||
|
_comments = new List<Comment>
|
||||||
|
{
|
||||||
|
new Comment
|
||||||
|
{
|
||||||
|
Id = 0,
|
||||||
|
DateCreated = DateTimeOffset.UtcNow,
|
||||||
|
Text = "This text contains null somewhere in the middle of a string",
|
||||||
|
Author = "Dog",
|
||||||
|
},
|
||||||
|
new Comment
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
DateCreated = DateTimeOffset.UtcNow,
|
||||||
|
Text = "null is here in the text",
|
||||||
|
Author = "Cat",
|
||||||
|
},
|
||||||
|
new Comment
|
||||||
|
{
|
||||||
|
Id = 2,
|
||||||
|
DateCreated = DateTimeOffset.UtcNow,
|
||||||
|
Text = "Regular comment without n*ll.",
|
||||||
|
Author = "Mouse",
|
||||||
|
},
|
||||||
|
new Comment
|
||||||
|
{
|
||||||
|
Id = 100,
|
||||||
|
DateCreated = DateTimeOffset.UtcNow,
|
||||||
|
Text = null,
|
||||||
|
Author = "null",
|
||||||
|
},
|
||||||
|
}.AsQueryable();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Filter_Equals_Null()
|
||||||
|
{
|
||||||
|
var model = new SieveModel {Filters = "Text==null"};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _comments);
|
||||||
|
|
||||||
|
Assert.Equal(100, result.Single().Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Filter_NotEquals_Null()
|
||||||
|
{
|
||||||
|
var model = new SieveModel {Filters = "Text!=null"};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _comments);
|
||||||
|
|
||||||
|
Assert.Equal(new[] {0, 1, 2}, result.Select(p => p.Id));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("Text@=null")]
|
||||||
|
[InlineData("Text@=*null")]
|
||||||
|
[InlineData("Text@=*NULL")]
|
||||||
|
[InlineData("Text@=*NulL")]
|
||||||
|
[InlineData("Text@=*null|text")]
|
||||||
|
public void Filter_Contains_NullString(string filter)
|
||||||
|
{
|
||||||
|
var model = new SieveModel {Filters = filter};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _comments);
|
||||||
|
|
||||||
|
Assert.Equal(new[] {0, 1}, result.Select(p => p.Id));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("Text|Author==null", 100)]
|
||||||
|
[InlineData("Text|Author@=null", 0, 1, 100)]
|
||||||
|
[InlineData("Text|Author@=*null", 0, 1, 100)]
|
||||||
|
[InlineData("Text|Author_=null", 1, 100)]
|
||||||
|
[InlineData("Text|Author_=*null", 1, 100)]
|
||||||
|
public void MultiFilter_Contains_NullString(string filter, params int[] expectedIds)
|
||||||
|
{
|
||||||
|
var model = new SieveModel {Filters = filter};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _comments);
|
||||||
|
|
||||||
|
Assert.Equal(expectedIds, result.Select(p => p.Id));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("Text_=null")]
|
||||||
|
[InlineData("Text_=*null")]
|
||||||
|
[InlineData("Text_=*NULL")]
|
||||||
|
[InlineData("Text_=*NulL")]
|
||||||
|
[InlineData("Text_=*null|text")]
|
||||||
|
public void Filter_StartsWith_NullString(string filter)
|
||||||
|
{
|
||||||
|
var model = new SieveModel {Filters = filter};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _comments);
|
||||||
|
|
||||||
|
Assert.Equal(new[] {1}, result.Select(p => p.Id));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("Text!@=null")]
|
||||||
|
[InlineData("Text!@=*null")]
|
||||||
|
[InlineData("Text!@=*NULL")]
|
||||||
|
[InlineData("Text!@=*NulL")]
|
||||||
|
[InlineData("Text!@=*null|text")]
|
||||||
|
public void Filter_DoesNotContain_NullString(string filter)
|
||||||
|
{
|
||||||
|
var model = new SieveModel {Filters = filter};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _comments);
|
||||||
|
|
||||||
|
Assert.Equal(new[] {2}, result.Select(p => p.Id));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("Text!_=null")]
|
||||||
|
[InlineData("Text!_=*null")]
|
||||||
|
[InlineData("Text!_=*NULL")]
|
||||||
|
[InlineData("Text!_=*NulL")]
|
||||||
|
public void Filter_DoesNotStartsWith_NullString(string filter)
|
||||||
|
{
|
||||||
|
var model = new SieveModel {Filters = filter};
|
||||||
|
|
||||||
|
var result = _processor.Apply(model, _comments);
|
||||||
|
|
||||||
|
Assert.Equal(new[] {0, 2}, result.Select(p => p.Id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user