diff --git a/QueryKit.IntegrationTests/Tests/PropertyResolverTests.cs b/QueryKit.IntegrationTests/Tests/PropertyResolverTests.cs index 432436b..2e742ea 100644 --- a/QueryKit.IntegrationTests/Tests/PropertyResolverTests.cs +++ b/QueryKit.IntegrationTests/Tests/PropertyResolverTests.cs @@ -5,6 +5,8 @@ namespace QueryKit.IntegrationTests.Tests; using FluentAssertions; using Microsoft.EntityFrameworkCore; using SharedTestingHelper.Fakes; +using SharedTestingHelper.Fakes.Author; +using SharedTestingHelper.Fakes.Recipes; using WebApiTestProject.Entities; public class PropertyResolverTests : TestBase @@ -151,6 +153,33 @@ public async Task property_is_not_prevented_by_another_property_whose_query_name people[0].Id.Should().Be(fakePerson.Id); } + [Fact] + public async Task property_path_on_the_right_side_is_compared() + { + // Arrange + var testingServiceScope = new TestingServiceScope(); + var name = Guid.NewGuid().ToString(); + var matchingRecipe = new FakeRecipeBuilder() + .WithTitle(name) + .Build(); + matchingRecipe.SetAuthor(new FakeAuthorBuilder().WithName(name).Build()); + var otherRecipe = new FakeRecipeBuilder() + .WithTitle(name) + .Build(); + otherRecipe.SetAuthor(new FakeAuthorBuilder().WithName(Guid.NewGuid().ToString()).Build()); + await testingServiceScope.InsertAsync(matchingRecipe, otherRecipe); + + var input = $"""Title == "{name}" && Title == Author.Name"""; + + // Act + var recipes = await testingServiceScope.DbContext().Recipes + .ApplyQueryKitFilter(input) + .ToListAsync(); + + // Assert + recipes.Should().ContainSingle(x => x.Id == matchingRecipe.Id); + } + [Theory] [InlineData("first-name")] [InlineData("_first")] diff --git a/QueryKit.UnitTests/PropertyResolverTests.cs b/QueryKit.UnitTests/PropertyResolverTests.cs index 040943f..9cfad25 100644 --- a/QueryKit.UnitTests/PropertyResolverTests.cs +++ b/QueryKit.UnitTests/PropertyResolverTests.cs @@ -688,25 +688,13 @@ public void alias_replacement_replaces_a_query_name_in_a_nested_path() } [Fact] - public void property_path_on_the_right_side_throws() + public void property_path_on_the_right_side_is_compared() { var input = """Title == Author.Name"""; - var act = () => FilterParser.ParseFilter(input); + var filterExpression = FilterParser.ParseFilter(input); - act.Should().Throw() - .WithInnerException() - .WithMessage("*Equal is not defined for the types 'System.String' and*Author*"); - } - - [Fact] - public void unquoted_dotted_word_on_the_right_side_throws() - { - var input = """Title == foo.bar"""; - - var act = () => FilterParser.ParseFilter(input); - - act.Should().Throw().WithMessage("*Line 1, Column 13*"); + filterExpression.ToDisplayString().Should().Be("x => (x.Title == x.Author.Name)"); } [Fact] diff --git a/QueryKit/FilterParser.cs b/QueryKit/FilterParser.cs index 07ead9d..6afdb5a 100644 --- a/QueryKit/FilterParser.cs +++ b/QueryKit/FilterParser.cs @@ -364,7 +364,7 @@ from closingBracket in Parse.Char(']') .XOr(NumberParser.Select(v => new RightSideValue(v, false, CultureNumberPrefix(v)))) .XOr((RawStringLiteralParser.Or(DoubleQuoteParser)).Select(v => new RightSideValue(v, true))) .XOr(SquareBracketParser) - .XOr(Identifier.Select(v => new RightSideValue(v, false))); // Keep this last to try property paths only if nothing else matches + .XOr(Identifier.DelimitedBy(Parse.Char('.')).Select(v => new RightSideValue(string.Join(".", v), false))); // Keep this last to try property paths only if nothing else matches private static readonly Parser RightSideValueParser = from atSign in Parse.Char('@').Optional()