Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions QueryKit.IntegrationTests/Tests/PropertyResolverTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,4 +122,56 @@ public async Task query_name_that_is_not_a_plain_identifier_filters_by_its_prope
people.Should().ContainSingle();
people[0].Id.Should().Be(fakePerson.Id);
}

[Fact]
public async Task non_public_mapped_property_filters_in_the_database()
{
// Arrange
var testingServiceScope = new TestingServiceScope();
var nickname = new Faker().Lorem.Sentence();
var fakePerson = new FakeTestingPersonBuilder().Build();
fakePerson.Nickname = nickname;
var otherPerson = new FakeTestingPersonBuilder().Build();
otherPerson.Nickname = new Faker().Lorem.Sentence();
await testingServiceScope.InsertAsync(fakePerson, otherPerson);

var input = $"""nickname == "{nickname}" """;

// Act
var queryable = testingServiceScope.DbContext().People.ApplyQueryKitFilter(input);
var people = await queryable.ToListAsync();

// Assert
queryable.ToQueryString().Should().Contain("""p.nickname = """);
people.Should().ContainSingle();
people[0].Id.Should().Be(fakePerson.Id);
}

[Fact]
public async Task non_public_mapped_property_filters_when_unknown_properties_are_allowed()
{
// Arrange
var testingServiceScope = new TestingServiceScope();
var nickname = new Faker().Lorem.Sentence();
var fakePerson = new FakeTestingPersonBuilder().Build();
fakePerson.Nickname = nickname;
var otherPerson = new FakeTestingPersonBuilder().Build();
otherPerson.Nickname = new Faker().Lorem.Sentence();
await testingServiceScope.InsertAsync(fakePerson, otherPerson);

var input = $"""Nickname == "{nickname}" """;
var config = new QueryKitConfiguration(config =>
{
config.AllowUnknownProperties = true;
});

// Act
var people = await testingServiceScope.DbContext().People
.ApplyQueryKitFilter(input, config)
.ToListAsync();

// Assert
people.Should().ContainSingle();
people[0].Id.Should().Be(fakePerson.Id);
}
}
119 changes: 119 additions & 0 deletions QueryKit.UnitTests/PropertyResolverTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -721,4 +721,123 @@ public void unknown_property_in_arithmetic_throws_an_argument_exception()
act.Should().Throw<ArgumentException>()
.WithMessage("Property 'Nope' not found on type 'TestingPerson'");
}

[Theory]
[InlineData("InternalScore > 30", "x => (x.InternalScore > 30)")]
[InlineData("internalscore > 30", "x => (x.InternalScore > 30)")]
[InlineData("""ProtectedNote == "a" """, """x => (x.ProtectedNote == "a")""")]
[InlineData("secretRank == 7", "x => (x.secretRank == 7)")]
[InlineData("""Owner.InternalAlias == "Ann" """, """x => (x.Owner.InternalAlias == "Ann")""")]
[InlineData("(InternalScore, Rating) > 3", "x => ((x.InternalScore > 3) OrElse (x.Rating > 3))")]
public void non_public_member_filters_like_a_public_member(string input, string expected)
{
var filterExpression = FilterParser.ParseFilter<MemberLookupModel>(input);

filterExpression.ToDisplayString().Should().Be(expected);
}

[Fact]
public void non_public_member_filters_the_rows()
{
var models = new List<MemberLookupModel>
{
new(internalScore: 50, rank: 7),
new(internalScore: 20, rank: 3),
};

var result = models.ApplyQueryKitFilter("InternalScore > 30 && secretRank == 7").ToList();

result.Should().ContainSingle().Which.Should().BeSameAs(models[0]);
}

[Fact]
public void non_public_member_filters_when_unknown_properties_are_allowed()
{
var input = """secretRank > 100 || Rating == 1""";
var config = new QueryKitConfiguration(config =>
{
config.AllowUnknownProperties = true;
});

var filterExpression = FilterParser.ParseFilter<MemberLookupModel>(input, config);

filterExpression.ToDisplayString().Should().Be("x => ((x.secretRank > 100) OrElse (x.Rating == 1))");
}

[Fact]
public void query_name_on_a_non_public_member_filters_by_that_member()
{
var input = """score > 30""";
var config = new QueryKitConfiguration(config =>
{
config.Property<MemberLookupModel>(x => x.InternalScore).HasQueryName("score");
});

var filterExpression = FilterParser.ParseFilter<MemberLookupModel>(input, config);

filterExpression.ToDisplayString().Should().Be("x => (x.InternalScore > 30)");
}

[Fact]
public void public_property_matches_before_a_non_public_field_with_the_same_name()
{
var input = """rank == 1""";

var filterExpression = FilterParser.ParseFilter<MemberLookupModel>(input);

filterExpression.ToDisplayString().Should().Be("x => (x.Rank == 1)");
}

[Theory]
[InlineData("""Item == "x" """)]
[InlineData("""item == "x" """)]
[InlineData("""(Item, Rating) == "x" """)]
public void indexer_is_an_unknown_property(string input)
{
var act = () => FilterParser.ParseFilter<MemberLookupModel>(input);

act.Should().Throw<UnknownFilterPropertyException>()
.WithMessage("The filter property 'Item' was not recognized.");
}

[Fact]
public void indexer_clause_is_true_equals_true_when_unknown_properties_are_allowed()
{
var input = """Item == "x" """;
var config = new QueryKitConfiguration(config =>
{
config.AllowUnknownProperties = true;
});

var filterExpression = FilterParser.ParseFilter<MemberLookupModel>(input, config);

filterExpression.ToDisplayString().Should().Be("x => (True == True)");
}

private class MemberLookupOwner
{
internal string InternalAlias { get; set; } = "";
}

private class MemberLookupModel
{
public MemberLookupModel() { }

public MemberLookupModel(int internalScore, int rank)
{
InternalScore = internalScore;
secretRank = rank;
}

public int Rating { get; set; }
public int Rank { get; set; }
public MemberLookupOwner Owner { get; set; } = new();
internal int InternalScore { get; set; }
protected string ProtectedNote { get; set; } = "";
private int secretRank;
#pragma warning disable CS0169 // Never read - the field only tests that the public Rank property matches first
private int rank;
#pragma warning restore CS0169
public string this[string key] => key;
}
}
3 changes: 3 additions & 0 deletions QueryKit.WebApiTestProject/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("QueryKit.IntegrationTests")]
2 changes: 2 additions & 0 deletions QueryKit.WebApiTestProject/Database/PersonConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ public void Configure(EntityTypeBuilder<TestingPerson> builder)
.HasConversion(x => x.Value, x => new EmailAddress(x))
.HasColumnName("email")
.IsRequired(false);

builder.Property(x => x.Nickname);

builder.OwnsOne(x => x.PhysicalAddress, opts =>
{
Expand Down
1 change: 1 addition & 0 deletions QueryKit.WebApiTestProject/Entities/TestingPerson.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public class TestingPerson
public Guid Id { get; set; } = Guid.NewGuid();
public EmailAddress Email { get; set; } = null!;
public Address PhysicalAddress { get; set; } = null!;
internal string? Nickname { get; set; }
}

public enum BirthMonthEnum
Expand Down
Loading
Loading