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
34 changes: 33 additions & 1 deletion QueryKit.IntegrationTests/Tests/HasConversionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -392,4 +392,36 @@ public async Task can_filter_guid_with_contains_and_has_conversion()
people.Count.Should().Be(1);
people[0].Id.Should().Be(person.Id);
}
}

[Theory]
[InlineData("^^")]
[InlineData("!^^")]
public async Task can_filter_by_email_with_in_operators_and_has_conversion(string op)
{
// Arrange
var testingServiceScope = new TestingServiceScope();
var emailOne = $"{Guid.NewGuid()}@example.com";
var emailTwo = $"{Guid.NewGuid()}@example.com";
var personOne = new FakeTestingPersonBuilder().WithEmail(emailOne).Build();
var personTwo = new FakeTestingPersonBuilder().WithEmail(emailTwo).Build();
var personThree = new FakeTestingPersonBuilder().Build();

await testingServiceScope.InsertAsync(personOne, personTwo, personThree);

var input = $"""Email {op} ["{emailOne}", "{emailTwo}"]""";
var config = new QueryKitConfiguration(config =>
{
config.Property<TestingPerson>(x => x.Email).HasConversion<string>();
});
var ids = new[] { personOne.Id, personTwo.Id, personThree.Id };

// Act
var queryablePeople = testingServiceScope.DbContext().People;
var appliedQueryable = queryablePeople.ApplyQueryKitFilter(input, config).Where(x => ids.Contains(x.Id));
var people = await appliedQueryable.ToListAsync();

// Assert
var expectedIds = op == "^^" ? new[] { personOne.Id, personTwo.Id } : new[] { personThree.Id };
people.Select(x => x.Id).Should().BeEquivalentTo(expectedIds);
}
}
122 changes: 122 additions & 0 deletions QueryKit.UnitTests/HasConversionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,128 @@ public void guid_with_has_conversion_compares_a_guid_constant(string queryName)
filter.ToDisplayString().Should().Be($"x => (x.Id == {KnownGuid})");
}

[Theory]
[InlineData("""Id ^^ ["2","3"]""", new[] { "two" })]
[InlineData("""Id !^^ ["2","3"]""", new[] { "one" })]
[InlineData("""Id ^^ [2, 3]""", new[] { "two" })]
public void struct_with_has_conversion_reads_a_list_for_the_in_operators(string input, string[] expectedNames)
{
// Arrange
var config = new QueryKitConfiguration(config =>
{
config.Property<WrappedIdRow>(x => x.Id).HasConversion<string>();
});

// Act
var result = WrappedIdRows().ApplyQueryKitFilter(input, config).ToList();

// Assert
result.Select(x => x.Name).Should().BeEquivalentTo(expectedNames);
}

[Fact]
public void struct_with_query_name_and_has_conversion_reads_a_list_for_the_in_operator()
{
// Arrange
var config = new QueryKitConfiguration(config =>
{
config.Property<WrappedIdRow>(x => x.Id).HasQueryName("wrappedid").HasConversion<string>();
});

// Act
var result = WrappedIdRows().ApplyQueryKitFilter("""wrappedid ^^ ["1","2"]""", config).ToList();

// Assert
result.Select(x => x.Name).Should().BeEquivalentTo("one", "two");
}

[Fact]
public void nullable_struct_with_has_conversion_reads_a_list_for_the_in_operator()
{
// Arrange
var rows = new List<NullableWrappedIdRow>
{
new() { Id = new WrappedId(1) },
new() { Id = new WrappedId(2) },
new() { Id = null }
};
var config = new QueryKitConfiguration(config =>
{
config.Property<NullableWrappedIdRow>(x => x.Id!).HasConversion<string>();
});

// Act
var result = rows.ApplyQueryKitFilter("""Id ^^ ["2","3"]""", config).ToList();

// Assert
result.Select(x => x.Id).Should().BeEquivalentTo(new WrappedId?[] { new WrappedId(2) });
}

[Theory]
[InlineData("""Email ^^ ["b@x.com","c@x.com"]""", new[] { "b@x.com" })]
[InlineData("""Email ^^ ["a,b@x.com"]""", new[] { "a,b@x.com" })]
[InlineData("""Email ^^ ["null"]""", new[] { "null" })]
[InlineData(""""Email ^^ [""" "q" """]"""", new[] { "\"q\"" })]
public void reference_type_with_has_conversion_reads_each_list_item_as_text(string input, string[] expectedEmails)
{
// Arrange
var rows = EmailRows();
rows.Add(new EmailRow { Email = new EmailAddressRecord("a,b@x.com") });
rows.Add(new EmailRow { Email = new EmailAddressRecord("null") });
rows.Add(new EmailRow { Email = new EmailAddressRecord("\"q\"") });
rows.Add(new EmailRow { Email = new EmailAddressRecord("q") });
rows.Add(new EmailRow { Email = null });
var config = new QueryKitConfiguration(config =>
{
config.Property<EmailRow>(x => x.Email!).HasConversion<string>();
});

// Act
var result = rows.ApplyQueryKitFilter(input, config).ToList();

// Assert
result.Select(x => x.Email?.Value).Should().BeEquivalentTo(expectedEmails);
}

[Theory]
[InlineData("""Email == "[a]" """)]
[InlineData("""Email == [a]""")]
public void text_in_brackets_stays_one_value_for_equals_with_has_conversion(string input)
{
// Arrange
var rows = EmailRows();
rows.Add(new EmailRow { Email = new EmailAddressRecord("[a]") });
var config = new QueryKitConfiguration(config =>
{
config.Property<EmailRow>(x => x.Email!).HasConversion<string>();
});

// Act
var result = rows.ApplyQueryKitFilter(input, config).ToList();

// Assert
result.Select(x => x.Email!.Value).Should().BeEquivalentTo("[a]");
}

[Fact]
public void list_item_that_does_not_convert_with_has_conversion_throws_parsing_exception()
{
// Arrange
var config = new QueryKitConfiguration(config =>
{
config.Property<WrappedIdRow>(x => x.Id).HasConversion<string>();
});

// Act
var act = () => WrappedIdRows().ApplyQueryKitFilter("""Id ^^ ["2","abc"]""", config).ToList();

// Assert
var exception = act.Should().Throw<ParsingException>().Which;
exception.Value.Should().Be("abc");
exception.PropertyName.Should().Be("Id");
exception.TargetType.Should().Be(typeof(WrappedId));
}

private static List<WrappedIdRow> WrappedIdRows() => new()
{
new() { Id = new WrappedId(1), Name = "one" },
Expand Down
9 changes: 9 additions & 0 deletions QueryKit/FilterParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,15 @@ private static Expression CreateRightExprForProperty(Expression leftExpr, string
var stringCtor = (underlyingType ?? leftExpr.Type).GetConstructor(new[] { typeof(string) });
if (stringCtor != null)
{
if (op.TakesList() && IsListValue(right, rightIsQuotedLiteral, op))
{
var constructedType = underlyingType ?? leftExpr.Type;
var items = SplitListItems(right).Select(item => Expression.Constant(
ConvertValue(item, constructedType, () => stringCtor.Invoke(BindingFlags.DoNotWrapExceptions, null, new object[] { item }, null)),
leftExpr.Type));
return Expression.NewArrayInit(leftExpr.Type, items);
}

Expression constructed = Expression.New(stringCtor, FilterValue.Create(right, typeof(string)));
return underlyingType == null ? constructed : Expression.Convert(constructed, leftExpr.Type);
}
Expand Down
Loading