@@ -64,7 +64,7 @@ public void SeparateIncludeFilters_WithOnlyMainFilters_ReturnsMainFilters()
6464 [ Fact ]
6565 public void SeparateIncludeFilters_WithSimpleIncludeFilter_SeparatesCorrectly ( )
6666 {
67- // Arrange
67+ // Arrange - Include filters must have IsIncludeFilter=true (set by parser for bracket syntax)
6868 var filters = new FilterGroup
6969 {
7070 Filters = new List < FilterParameter >
@@ -80,6 +80,7 @@ public void SeparateIncludeFilters_WithSimpleIncludeFilter_SeparatesCorrectly()
8080 Field = "comments.status" ,
8181 Operator = FilterOperator . Eq ,
8282 Value = "approved" ,
83+ IsIncludeFilter = true , // Bracket syntax: filter[comments][status][eq]=approved
8384 } ,
8485 } ,
8586 } ;
@@ -103,10 +104,51 @@ public void SeparateIncludeFilters_WithSimpleIncludeFilter_SeparatesCorrectly()
103104 Assert . Equal ( "approved" , includeFilters [ 0 ] . FilterGroup . Filters [ 0 ] . Value ) ;
104105 }
105106
107+ [ Fact ]
108+ public void SeparateIncludeFilters_WithDotNotation_TreatedAsPrimaryFilter ( )
109+ {
110+ // Arrange - Dot notation filters are primary filters (filter main resource through relationship)
111+ var filters = new FilterGroup
112+ {
113+ Filters = new List < FilterParameter >
114+ {
115+ new ( )
116+ {
117+ Field = "title" ,
118+ Operator = FilterOperator . Eq ,
119+ Value = "Test" ,
120+ } ,
121+ new ( )
122+ {
123+ Field = "comments.status" , // Dot notation without IsIncludeFilter = primary filter
124+ Operator = FilterOperator . Eq ,
125+ Value = "approved" ,
126+ IsIncludeFilter = false ,
127+ } ,
128+ } ,
129+ } ;
130+ var includePaths = new List < string > { "comments" } ;
131+
132+ // Act
133+ var ( mainFilters , includeFilters ) = IncludeFilterParser . SeparateIncludeFilters (
134+ filters ,
135+ includePaths
136+ ) ;
137+
138+ // Assert - Both filters should be main filters (dot notation = primary filter)
139+ Assert . NotNull ( mainFilters ) ;
140+ Assert . Equal ( 2 , mainFilters . Filters . Count ) ;
141+ Assert . Contains ( mainFilters . Filters , f => f . Field == "title" ) ;
142+ Assert . Contains ( mainFilters . Filters , f => f . Field == "comments.status" ) ;
143+
144+ // No include filters - dot notation is now primary filter
145+ Assert . Empty ( includeFilters ) ;
146+ }
147+
106148 [ Fact ]
107149 public void SeparateIncludeFilters_WithKebabCaseInclude_HandlesCorrectly ( )
108150 {
109- // Arrange
151+ // Arrange - Include filters must have IsIncludeFilter=true
110152 var filters = new FilterGroup
111153 {
112154 Filters = new List < FilterParameter >
@@ -116,6 +158,7 @@ public void SeparateIncludeFilters_WithKebabCaseInclude_HandlesCorrectly()
116158 Field = "cveComments.companyCode" ,
117159 Operator = FilterOperator . Eq ,
118160 Value = "AA" ,
161+ IsIncludeFilter = true , // Bracket syntax: filter[cveComments][companyCode][eq]=AA
119162 } ,
120163 } ,
121164 } ;
@@ -137,7 +180,7 @@ public void SeparateIncludeFilters_WithKebabCaseInclude_HandlesCorrectly()
137180 [ Fact ]
138181 public void SeparateIncludeFilters_WithNestedIncludeFilter_SeparatesCorrectly ( )
139182 {
140- // Arrange
183+ // Arrange - Include filters must have IsIncludeFilter=true
141184 var filters = new FilterGroup
142185 {
143186 Filters = new List < FilterParameter >
@@ -147,6 +190,7 @@ public void SeparateIncludeFilters_WithNestedIncludeFilter_SeparatesCorrectly()
147190 Field = "comments.author.department" ,
148191 Operator = FilterOperator . Eq ,
149192 Value = "Security" ,
193+ IsIncludeFilter = true , // Bracket syntax for nested: filter[comments.author][department][eq]=Security
150194 } ,
151195 } ,
152196 } ;
@@ -168,7 +212,7 @@ public void SeparateIncludeFilters_WithNestedIncludeFilter_SeparatesCorrectly()
168212 [ Fact ]
169213 public void SeparateIncludeFilters_WithComplexOrFilter_HandlesCorrectly ( )
170214 {
171- // Arrange
215+ // Arrange - Include filters must have IsIncludeFilter=true
172216 var filters = new FilterGroup
173217 {
174218 LogicalOperator = LogicalOperator . Or ,
@@ -179,12 +223,14 @@ public void SeparateIncludeFilters_WithComplexOrFilter_HandlesCorrectly()
179223 Field = "comments.companyCode" ,
180224 Operator = FilterOperator . Eq ,
181225 Value = "AA" ,
226+ IsIncludeFilter = true ,
182227 } ,
183228 new ( )
184229 {
185230 Field = "comments.companyCode" ,
186231 Operator = FilterOperator . IsNull ,
187232 Value = "true" ,
233+ IsIncludeFilter = true ,
188234 } ,
189235 } ,
190236 } ;
@@ -201,13 +247,16 @@ public void SeparateIncludeFilters_WithComplexOrFilter_HandlesCorrectly()
201247 Assert . Equal ( "comments" , includeFilters [ 0 ] . RelationshipPath ) ;
202248 Assert . Equal ( LogicalOperator . Or , includeFilters [ 0 ] . FilterGroup . LogicalOperator ) ;
203249 Assert . Equal ( 2 , includeFilters [ 0 ] . FilterGroup . Filters . Count ) ;
204- Assert . All ( includeFilters [ 0 ] . FilterGroup . Filters , f => Assert . Equal ( "companyCode" , f . Field ) ) ;
250+ Assert . All (
251+ includeFilters [ 0 ] . FilterGroup . Filters ,
252+ f => Assert . Equal ( "companyCode" , f . Field )
253+ ) ;
205254 }
206255
207256 [ Fact ]
208257 public void SeparateIncludeFilters_WithFilterOnNonIncludedRelationship_ReturnsAsMainFilter ( )
209258 {
210- // Arrange
259+ // Arrange - Even with IsIncludeFilter=true, if relationship is not included, it becomes main filter
211260 var filters = new FilterGroup
212261 {
213262 Filters = new List < FilterParameter >
@@ -217,6 +266,7 @@ public void SeparateIncludeFilters_WithFilterOnNonIncludedRelationship_ReturnsAs
217266 Field = "comments.status" ,
218267 Operator = FilterOperator . Eq ,
219268 Value = "approved" ,
269+ IsIncludeFilter = true , // Marked as include filter but relationship not in includes
220270 } ,
221271 } ,
222272 } ;
@@ -229,7 +279,7 @@ public void SeparateIncludeFilters_WithFilterOnNonIncludedRelationship_ReturnsAs
229279 ) ;
230280
231281 // Assert
232- // When the relationship is not included, the filter should be treated as a main filter with dot notation
282+ // When the relationship is not included, the filter should be treated as a main filter
233283 Assert . NotNull ( mainFilters ) ;
234284 Assert . Single ( mainFilters . Filters ) ;
235285 Assert . Equal ( "comments.status" , mainFilters . Filters [ 0 ] . Field ) ;
@@ -272,7 +322,7 @@ public void SeparateIncludeFilters_WithTooManyOrConditions_ThrowsException()
272322 [ Fact ]
273323 public void SeparateIncludeFilters_WithTooDeepNesting_ThrowsException ( )
274324 {
275- // Arrange
325+ // Arrange - Include filters must have IsIncludeFilter=true for depth checking
276326 var filters = new FilterGroup
277327 {
278328 Filters = new List < FilterParameter >
@@ -282,7 +332,8 @@ public void SeparateIncludeFilters_WithTooDeepNesting_ThrowsException()
282332 Field = "a.b.c.d.e" ,
283333 Operator = FilterOperator . Eq ,
284334 Value = "test" ,
285- } , // 5 levels deep
335+ IsIncludeFilter = true , // 5 levels deep
336+ } ,
286337 } ,
287338 } ;
288339 var includePaths = new List < string > { "a.b.c.d" } ;
@@ -298,7 +349,7 @@ public void SeparateIncludeFilters_WithTooDeepNesting_ThrowsException()
298349 [ Fact ]
299350 public void SeparateIncludeFilters_WithMixedMainAndIncludeFilters_SeparatesCorrectly ( )
300351 {
301- // Arrange
352+ // Arrange - Include filters must have IsIncludeFilter=true
302353 var filters = new FilterGroup
303354 {
304355 Filters = new List < FilterParameter >
@@ -314,6 +365,7 @@ public void SeparateIncludeFilters_WithMixedMainAndIncludeFilters_SeparatesCorre
314365 Field = "comments.approved" ,
315366 Operator = FilterOperator . Eq ,
316367 Value = "true" ,
368+ IsIncludeFilter = true , // Bracket syntax: filter[comments][approved][eq]=true
317369 } ,
318370 new ( )
319371 {
@@ -346,7 +398,7 @@ public void SeparateIncludeFilters_WithMixedMainAndIncludeFilters_SeparatesCorre
346398 [ Fact ]
347399 public void SeparateIncludeFilters_WithNestedGroups_HandlesCorrectly ( )
348400 {
349- // Arrange
401+ // Arrange - Include filters must have IsIncludeFilter=true
350402 var filters = new FilterGroup
351403 {
352404 LogicalOperator = LogicalOperator . And ,
@@ -371,12 +423,14 @@ public void SeparateIncludeFilters_WithNestedGroups_HandlesCorrectly()
371423 Field = "comments.status" ,
372424 Operator = FilterOperator . Eq ,
373425 Value = "approved" ,
426+ IsIncludeFilter = true ,
374427 } ,
375428 new ( )
376429 {
377430 Field = "comments.status" ,
378431 Operator = FilterOperator . Eq ,
379432 Value = "pending" ,
433+ IsIncludeFilter = true ,
380434 } ,
381435 } ,
382436 } ,
@@ -406,7 +460,7 @@ public void SeparateIncludeFilters_WithNestedGroups_HandlesCorrectly()
406460 [ Fact ]
407461 public void SeparateIncludeFilters_WithDeepNestedIncludeFilterUsingLeafName_SeparatesCorrectly ( )
408462 {
409- // Arrange - This tests the scenario: include=cve,cve.cvecomments&filter[cvecomments. companyCode][eq]=AA
463+ // Arrange - This tests the scenario: include=cve,cve.cvecomments&filter[cvecomments][ companyCode][eq]=AA
410464 var filters = new FilterGroup
411465 {
412466 Filters = new List < FilterParameter >
@@ -416,6 +470,7 @@ public void SeparateIncludeFilters_WithDeepNestedIncludeFilterUsingLeafName_Sepa
416470 Field = "cvecomments.companyCode" ,
417471 Operator = FilterOperator . Eq ,
418472 Value = "AA" ,
473+ IsIncludeFilter = true , // Bracket syntax: filter[cvecomments][companyCode][eq]=AA
419474 } ,
420475 } ,
421476 } ;
@@ -449,6 +504,7 @@ public void SeparateIncludeFilters_WithDeepNestedIncludeFilterUsingKebabCase_Sep
449504 Field = "cveComments.companyCode" ,
450505 Operator = FilterOperator . Eq ,
451506 Value = "AA" ,
507+ IsIncludeFilter = true , // Bracket syntax: filter[cveComments][companyCode][eq]=AA
452508 } ,
453509 } ,
454510 } ;
@@ -482,12 +538,14 @@ public void SeparateIncludeFilters_WithMultipleDeepNestedFilters_SeparatesCorrec
482538 Field = "author.name" ,
483539 Operator = FilterOperator . Eq ,
484540 Value = "John" ,
541+ IsIncludeFilter = true , // Bracket syntax
485542 } ,
486543 new ( )
487544 {
488545 Field = "comments.status" ,
489546 Operator = FilterOperator . Eq ,
490547 Value = "approved" ,
548+ IsIncludeFilter = true , // Bracket syntax
491549 } ,
492550 } ,
493551 } ;
0 commit comments