Skip to content

Commit 3c154c7

Browse files
authored
feat: case insensitive operator translation config
2 parents de0c8a9 + 01510a3 commit 3c154c7

File tree

9 files changed

+686
-70
lines changed

9 files changed

+686
-70
lines changed

QueryKit.IntegrationTests/Tests/DatabaseFilteringTests.cs

Lines changed: 348 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3853,4 +3853,352 @@ public async Task can_filter_with_derived_property_containing_complex_conditiona
38533853
pastPeople.Should().Contain(p => p.Id == fakePersonTwo.Id, "past date should match < 0");
38543854
}
38553855

3856+
[Fact]
3857+
public async Task case_insensitive_upper_mode_finds_uppercase_data_with_lowercase_filter()
3858+
{
3859+
// Arrange
3860+
var testingServiceScope = new TestingServiceScope();
3861+
var uniqueSuffix = Guid.NewGuid().ToString("N")[..8].ToUpper();
3862+
var uppercaseTitle = $"UNIQUE TITLE {uniqueSuffix}";
3863+
3864+
var fakePersonOne = new FakeTestingPersonBuilder()
3865+
.WithTitle(uppercaseTitle)
3866+
.Build();
3867+
var fakePersonTwo = new FakeTestingPersonBuilder().Build();
3868+
await testingServiceScope.InsertAsync(fakePersonOne, fakePersonTwo);
3869+
3870+
// Search with a lowercase version of the unique suffix
3871+
var lowercaseSearchValue = uniqueSuffix.ToLower();
3872+
var input = $"""Title @=* "{lowercaseSearchValue}" """;
3873+
3874+
var config = new QueryKitConfiguration(settings =>
3875+
{
3876+
settings.CaseInsensitiveComparison = CaseInsensitiveMode.Upper;
3877+
});
3878+
3879+
// Act
3880+
var queryablePeople = testingServiceScope.DbContext().People;
3881+
var people = await queryablePeople.ApplyQueryKitFilter(input, config).ToListAsync();
3882+
3883+
// Assert - Upper mode: UPPER(title) contains UPPER(searchValue) → should match
3884+
people.Count.Should().Be(1);
3885+
people[0].Id.Should().Be(fakePersonOne.Id);
3886+
}
3887+
3888+
[Fact]
3889+
public async Task case_insensitive_per_property_upper_mode_finds_uppercase_data()
3890+
{
3891+
// Arrange
3892+
var testingServiceScope = new TestingServiceScope();
3893+
var uniqueSuffix = Guid.NewGuid().ToString("N")[..8].ToUpper();
3894+
var uppercaseTitle = $"PERPROPTITLE {uniqueSuffix}";
3895+
3896+
var fakePersonOne = new FakeTestingPersonBuilder()
3897+
.WithTitle(uppercaseTitle)
3898+
.Build();
3899+
var fakePersonTwo = new FakeTestingPersonBuilder().Build();
3900+
await testingServiceScope.InsertAsync(fakePersonOne, fakePersonTwo);
3901+
3902+
var lowerSearch = uniqueSuffix.ToLower();
3903+
var input = $"""Title @=* "{lowerSearch}" """;
3904+
3905+
// Global mode is Lower, but Title is overridden to Upper
3906+
var config = new QueryKitConfiguration(settings =>
3907+
{
3908+
settings.CaseInsensitiveComparison = CaseInsensitiveMode.Lower;
3909+
settings.Property<TestingPerson>(x => x.Title).HasCaseInsensitiveMode(CaseInsensitiveMode.Upper);
3910+
});
3911+
3912+
// Act
3913+
var queryablePeople = testingServiceScope.DbContext().People;
3914+
var people = await queryablePeople.ApplyQueryKitFilter(input, config).ToListAsync();
3915+
3916+
// Assert - per-property Upper mode: UPPER(title) contains UPPER(searchValue) → should match
3917+
people.Count.Should().Be(1);
3918+
people[0].Id.Should().Be(fakePersonOne.Id);
3919+
}
3920+
3921+
[Fact]
3922+
public async Task case_insensitive_upper_mode_equals_operator_finds_uppercase_data()
3923+
{
3924+
// Arrange
3925+
var testingServiceScope = new TestingServiceScope();
3926+
var uniqueSuffix = Guid.NewGuid().ToString("N")[..8].ToUpper();
3927+
var uppercaseTitle = $"EQTITLE {uniqueSuffix}";
3928+
3929+
var fakePersonOne = new FakeTestingPersonBuilder().WithTitle(uppercaseTitle).Build();
3930+
var fakePersonTwo = new FakeTestingPersonBuilder().Build();
3931+
await testingServiceScope.InsertAsync(fakePersonOne, fakePersonTwo);
3932+
3933+
var input = $"""Title ==* "{uppercaseTitle.ToLower()}" """;
3934+
var config = new QueryKitConfiguration(s => s.CaseInsensitiveComparison = CaseInsensitiveMode.Upper);
3935+
3936+
// Act
3937+
var people = await testingServiceScope.DbContext().People
3938+
.ApplyQueryKitFilter(input, config).ToListAsync();
3939+
3940+
// Assert
3941+
people.Count.Should().Be(1);
3942+
people[0].Id.Should().Be(fakePersonOne.Id);
3943+
}
3944+
3945+
[Fact]
3946+
public async Task case_insensitive_upper_mode_not_equals_operator_excludes_matching_record()
3947+
{
3948+
// Arrange
3949+
var testingServiceScope = new TestingServiceScope();
3950+
var uniqueSuffix = Guid.NewGuid().ToString("N")[..8].ToUpper();
3951+
var uppercaseTitle = $"NEQTITLE {uniqueSuffix}";
3952+
3953+
var fakePersonOne = new FakeTestingPersonBuilder().WithTitle(uppercaseTitle).Build();
3954+
var fakePersonTwo = new FakeTestingPersonBuilder().WithTitle($"OTHER {uniqueSuffix}").Build();
3955+
await testingServiceScope.InsertAsync(fakePersonOne, fakePersonTwo);
3956+
3957+
// Search with lowercase — should exclude fakePersonOne
3958+
var input = $"""Title !=* "{uppercaseTitle.ToLower()}" """;
3959+
var config = new QueryKitConfiguration(s => s.CaseInsensitiveComparison = CaseInsensitiveMode.Upper);
3960+
3961+
// Act
3962+
var people = await testingServiceScope.DbContext().People
3963+
.ApplyQueryKitFilter(input, config).ToListAsync();
3964+
3965+
// Assert
3966+
people.Should().NotContain(p => p.Id == fakePersonOne.Id);
3967+
people.Should().Contain(p => p.Id == fakePersonTwo.Id);
3968+
}
3969+
3970+
[Fact]
3971+
public async Task case_insensitive_upper_mode_starts_with_operator_finds_uppercase_data()
3972+
{
3973+
// Arrange
3974+
var testingServiceScope = new TestingServiceScope();
3975+
var uniqueSuffix = Guid.NewGuid().ToString("N")[..8].ToUpper();
3976+
var uppercaseTitle = $"SWTITLE {uniqueSuffix}";
3977+
3978+
var fakePersonOne = new FakeTestingPersonBuilder().WithTitle(uppercaseTitle).Build();
3979+
var fakePersonTwo = new FakeTestingPersonBuilder().Build();
3980+
await testingServiceScope.InsertAsync(fakePersonOne, fakePersonTwo);
3981+
3982+
// Search with lowercase prefix
3983+
var input = $"""Title _=* "swtitle" """;
3984+
var config = new QueryKitConfiguration(s => s.CaseInsensitiveComparison = CaseInsensitiveMode.Upper);
3985+
3986+
// Act
3987+
var people = await testingServiceScope.DbContext().People
3988+
.ApplyQueryKitFilter(input, config).ToListAsync();
3989+
3990+
// Assert
3991+
people.Count.Should().BeGreaterOrEqualTo(1);
3992+
people.Should().Contain(p => p.Id == fakePersonOne.Id);
3993+
}
3994+
3995+
[Fact]
3996+
public async Task case_insensitive_upper_mode_ends_with_operator_finds_uppercase_data()
3997+
{
3998+
// Arrange
3999+
var testingServiceScope = new TestingServiceScope();
4000+
var uniqueSuffix = Guid.NewGuid().ToString("N")[..8].ToUpper();
4001+
var uppercaseTitle = $"EWTITLE {uniqueSuffix}";
4002+
4003+
var fakePersonOne = new FakeTestingPersonBuilder().WithTitle(uppercaseTitle).Build();
4004+
var fakePersonTwo = new FakeTestingPersonBuilder().Build();
4005+
await testingServiceScope.InsertAsync(fakePersonOne, fakePersonTwo);
4006+
4007+
// Search with lowercase suffix
4008+
var input = $"""Title _-=* "{uniqueSuffix.ToLower()}" """;
4009+
var config = new QueryKitConfiguration(s => s.CaseInsensitiveComparison = CaseInsensitiveMode.Upper);
4010+
4011+
// Act
4012+
var people = await testingServiceScope.DbContext().People
4013+
.ApplyQueryKitFilter(input, config).ToListAsync();
4014+
4015+
// Assert
4016+
people.Count.Should().BeGreaterOrEqualTo(1);
4017+
people.Should().Contain(p => p.Id == fakePersonOne.Id);
4018+
}
4019+
4020+
[Fact]
4021+
public async Task case_insensitive_upper_mode_not_contains_operator_excludes_matching_record()
4022+
{
4023+
// Arrange
4024+
var testingServiceScope = new TestingServiceScope();
4025+
var uniqueSuffix = Guid.NewGuid().ToString("N")[..8].ToUpper();
4026+
var uppercaseTitle = $"NOTCONTAINS {uniqueSuffix}";
4027+
4028+
var fakePersonOne = new FakeTestingPersonBuilder().WithTitle(uppercaseTitle).Build();
4029+
var fakePersonTwo = new FakeTestingPersonBuilder().WithTitle($"DIFFERENT {Guid.NewGuid():N}").Build();
4030+
await testingServiceScope.InsertAsync(fakePersonOne, fakePersonTwo);
4031+
4032+
// Lowercase search — Upper mode should still find the match and therefore exclude it
4033+
var input = $"""Title !@=* "{uniqueSuffix.ToLower()}" """;
4034+
var config = new QueryKitConfiguration(s => s.CaseInsensitiveComparison = CaseInsensitiveMode.Upper);
4035+
4036+
// Act
4037+
var people = await testingServiceScope.DbContext().People
4038+
.ApplyQueryKitFilter(input, config).ToListAsync();
4039+
4040+
// Assert
4041+
people.Should().NotContain(p => p.Id == fakePersonOne.Id);
4042+
people.Should().Contain(p => p.Id == fakePersonTwo.Id);
4043+
}
4044+
4045+
[Fact]
4046+
public async Task case_insensitive_upper_mode_not_starts_with_operator_excludes_matching_record()
4047+
{
4048+
// Arrange
4049+
var testingServiceScope = new TestingServiceScope();
4050+
var uniqueSuffix = Guid.NewGuid().ToString("N")[..8].ToUpper();
4051+
var uppercaseTitle = $"NSWPREFIX {uniqueSuffix}";
4052+
4053+
var fakePersonOne = new FakeTestingPersonBuilder().WithTitle(uppercaseTitle).Build();
4054+
var fakePersonTwo = new FakeTestingPersonBuilder().WithTitle($"DIFFERENT {Guid.NewGuid():N}").Build();
4055+
await testingServiceScope.InsertAsync(fakePersonOne, fakePersonTwo);
4056+
4057+
var input = $"""Title !_=* "nswprefix" """;
4058+
var config = new QueryKitConfiguration(s => s.CaseInsensitiveComparison = CaseInsensitiveMode.Upper);
4059+
4060+
// Act
4061+
var people = await testingServiceScope.DbContext().People
4062+
.ApplyQueryKitFilter(input, config).ToListAsync();
4063+
4064+
// Assert
4065+
people.Should().NotContain(p => p.Id == fakePersonOne.Id);
4066+
people.Should().Contain(p => p.Id == fakePersonTwo.Id);
4067+
}
4068+
4069+
[Fact]
4070+
public async Task case_insensitive_upper_mode_not_ends_with_operator_excludes_matching_record()
4071+
{
4072+
// Arrange
4073+
var testingServiceScope = new TestingServiceScope();
4074+
var uniqueSuffix = Guid.NewGuid().ToString("N")[..8].ToUpper();
4075+
var uppercaseTitle = $"NEWSUFFIX {uniqueSuffix}";
4076+
4077+
var fakePersonOne = new FakeTestingPersonBuilder().WithTitle(uppercaseTitle).Build();
4078+
var fakePersonTwo = new FakeTestingPersonBuilder().WithTitle($"DIFFERENT {Guid.NewGuid():N}").Build();
4079+
await testingServiceScope.InsertAsync(fakePersonOne, fakePersonTwo);
4080+
4081+
var input = $"""Title !_-=* "{uniqueSuffix.ToLower()}" """;
4082+
var config = new QueryKitConfiguration(s => s.CaseInsensitiveComparison = CaseInsensitiveMode.Upper);
4083+
4084+
// Act
4085+
var people = await testingServiceScope.DbContext().People
4086+
.ApplyQueryKitFilter(input, config).ToListAsync();
4087+
4088+
// Assert
4089+
people.Should().NotContain(p => p.Id == fakePersonOne.Id);
4090+
people.Should().Contain(p => p.Id == fakePersonTwo.Id);
4091+
}
4092+
4093+
[Fact]
4094+
public async Task case_insensitive_upper_mode_in_operator_finds_uppercase_data()
4095+
{
4096+
// Arrange
4097+
var testingServiceScope = new TestingServiceScope();
4098+
var uniqueSuffix = Guid.NewGuid().ToString("N")[..8].ToUpper();
4099+
var uppercaseTitle = $"INTITLE {uniqueSuffix}";
4100+
4101+
var fakePersonOne = new FakeTestingPersonBuilder().WithTitle(uppercaseTitle).Build();
4102+
var fakePersonTwo = new FakeTestingPersonBuilder().Build();
4103+
await testingServiceScope.InsertAsync(fakePersonOne, fakePersonTwo);
4104+
4105+
// Provide lowercase version in the list
4106+
var input = $"""Title ^^* ["{uppercaseTitle.ToLower()}"]""";
4107+
var config = new QueryKitConfiguration(s => s.CaseInsensitiveComparison = CaseInsensitiveMode.Upper);
4108+
4109+
// Act
4110+
var people = await testingServiceScope.DbContext().People
4111+
.ApplyQueryKitFilter(input, config).ToListAsync();
4112+
4113+
// Assert
4114+
people.Count.Should().BeGreaterOrEqualTo(1);
4115+
people.Should().Contain(p => p.Id == fakePersonOne.Id);
4116+
}
4117+
4118+
[Fact]
4119+
public async Task case_insensitive_upper_mode_not_in_operator_excludes_matching_record()
4120+
{
4121+
// Arrange
4122+
var testingServiceScope = new TestingServiceScope();
4123+
var uniqueSuffix = Guid.NewGuid().ToString("N")[..8].ToUpper();
4124+
var uppercaseTitle = $"NOTINTITLE {uniqueSuffix}";
4125+
4126+
var fakePersonOne = new FakeTestingPersonBuilder().WithTitle(uppercaseTitle).Build();
4127+
var fakePersonTwo = new FakeTestingPersonBuilder().WithTitle($"DIFFERENT {Guid.NewGuid():N}").Build();
4128+
await testingServiceScope.InsertAsync(fakePersonOne, fakePersonTwo);
4129+
4130+
// Lowercase version in exclusion list — should still exclude fakePersonOne
4131+
var input = $"""Title !^^* ["{uppercaseTitle.ToLower()}"]""";
4132+
var config = new QueryKitConfiguration(s => s.CaseInsensitiveComparison = CaseInsensitiveMode.Upper);
4133+
4134+
// Act
4135+
var people = await testingServiceScope.DbContext().People
4136+
.ApplyQueryKitFilter(input, config).ToListAsync();
4137+
4138+
// Assert
4139+
people.Should().NotContain(p => p.Id == fakePersonOne.Id);
4140+
people.Should().Contain(p => p.Id == fakePersonTwo.Id);
4141+
}
4142+
4143+
[Fact]
4144+
public async Task case_insensitive_per_property_lower_overrides_global_upper()
4145+
{
4146+
// Arrange
4147+
var testingServiceScope = new TestingServiceScope();
4148+
var uniqueSuffix = Guid.NewGuid().ToString("N")[..8].ToLower();
4149+
var lowercaseTitle = $"lowertitle {uniqueSuffix}";
4150+
4151+
var fakePersonOne = new FakeTestingPersonBuilder().WithTitle(lowercaseTitle).Build();
4152+
var fakePersonTwo = new FakeTestingPersonBuilder().Build();
4153+
await testingServiceScope.InsertAsync(fakePersonOne, fakePersonTwo);
4154+
4155+
// Search with uppercase — per-property Lower (lower(data) == lower(search)) should still match
4156+
var input = $"""Title @=* "{uniqueSuffix.ToUpper()}" """;
4157+
var config = new QueryKitConfiguration(settings =>
4158+
{
4159+
settings.CaseInsensitiveComparison = CaseInsensitiveMode.Upper;
4160+
settings.Property<TestingPerson>(x => x.Title).HasCaseInsensitiveMode(CaseInsensitiveMode.Lower);
4161+
});
4162+
4163+
// Act
4164+
var people = await testingServiceScope.DbContext().People
4165+
.ApplyQueryKitFilter(input, config).ToListAsync();
4166+
4167+
// Assert - even though global is Upper, per-property Lower means lower(title) contains lower(search) → match
4168+
people.Count.Should().BeGreaterOrEqualTo(1);
4169+
people.Should().Contain(p => p.Id == fakePersonOne.Id);
4170+
}
4171+
4172+
[Fact]
4173+
public async Task case_insensitive_per_property_overrides_independent_of_other_properties()
4174+
{
4175+
// Arrange
4176+
var testingServiceScope = new TestingServiceScope();
4177+
var uniqueTitleSuffix = Guid.NewGuid().ToString("N")[..8].ToUpper();
4178+
var uniqueFirstName = $"FIRSTNAME{Guid.NewGuid().ToString("N")[..8].ToUpper()}";
4179+
4180+
var fakePersonOne = new FakeTestingPersonBuilder()
4181+
.WithTitle($"OVERTITLE {uniqueTitleSuffix}")
4182+
.WithFirstName(uniqueFirstName)
4183+
.Build();
4184+
var fakePersonTwo = new FakeTestingPersonBuilder().Build();
4185+
await testingServiceScope.InsertAsync(fakePersonOne, fakePersonTwo);
4186+
4187+
// Title uses per-property Upper (global is Lower); FirstName uses global Lower
4188+
var input = $"""Title @=* "{uniqueTitleSuffix.ToLower()}" && FirstName ==* "{uniqueFirstName.ToLower()}" """;
4189+
var config = new QueryKitConfiguration(settings =>
4190+
{
4191+
settings.CaseInsensitiveComparison = CaseInsensitiveMode.Lower;
4192+
settings.Property<TestingPerson>(x => x.Title).HasCaseInsensitiveMode(CaseInsensitiveMode.Upper);
4193+
});
4194+
4195+
// Act
4196+
var people = await testingServiceScope.DbContext().People
4197+
.ApplyQueryKitFilter(input, config).ToListAsync();
4198+
4199+
// Assert - both conditions resolve correctly despite using different modes
4200+
people.Count.Should().Be(1);
4201+
people[0].Id.Should().Be(fakePersonOne.Id);
4202+
}
4203+
38564204
}

0 commit comments

Comments
 (0)