@@ -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