@@ -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 ) ;
@@ -239,7 +289,7 @@ public void SeparateIncludeFilters_WithFilterOnNonIncludedRelationship_ReturnsAs
239289 [ Fact ]
240290 public void SeparateIncludeFilters_WithTooDeepNesting_ThrowsException ( )
241291 {
242- // Arrange
292+ // Arrange - Include filters must have IsIncludeFilter=true for depth checking
243293 var filters = new FilterGroup
244294 {
245295 Filters = new List < FilterParameter >
@@ -249,7 +299,8 @@ public void SeparateIncludeFilters_WithTooDeepNesting_ThrowsException()
249299 Field = "a.b.c.d.e" ,
250300 Operator = FilterOperator . Eq ,
251301 Value = "test" ,
252- } , // 5 levels deep
302+ IsIncludeFilter = true , // 5 levels deep
303+ } ,
253304 } ,
254305 } ;
255306 var includePaths = new List < string > { "a.b.c.d" } ;
@@ -265,7 +316,7 @@ public void SeparateIncludeFilters_WithTooDeepNesting_ThrowsException()
265316 [ Fact ]
266317 public void SeparateIncludeFilters_WithMixedMainAndIncludeFilters_SeparatesCorrectly ( )
267318 {
268- // Arrange
319+ // Arrange - Include filters must have IsIncludeFilter=true
269320 var filters = new FilterGroup
270321 {
271322 Filters = new List < FilterParameter >
@@ -281,6 +332,7 @@ public void SeparateIncludeFilters_WithMixedMainAndIncludeFilters_SeparatesCorre
281332 Field = "comments.approved" ,
282333 Operator = FilterOperator . Eq ,
283334 Value = "true" ,
335+ IsIncludeFilter = true , // Bracket syntax: filter[comments][approved][eq]=true
284336 } ,
285337 new ( )
286338 {
@@ -313,7 +365,7 @@ public void SeparateIncludeFilters_WithMixedMainAndIncludeFilters_SeparatesCorre
313365 [ Fact ]
314366 public void SeparateIncludeFilters_WithNestedGroups_HandlesCorrectly ( )
315367 {
316- // Arrange
368+ // Arrange - Include filters must have IsIncludeFilter=true
317369 var filters = new FilterGroup
318370 {
319371 LogicalOperator = LogicalOperator . And ,
@@ -338,12 +390,14 @@ public void SeparateIncludeFilters_WithNestedGroups_HandlesCorrectly()
338390 Field = "comments.status" ,
339391 Operator = FilterOperator . Eq ,
340392 Value = "approved" ,
393+ IsIncludeFilter = true ,
341394 } ,
342395 new ( )
343396 {
344397 Field = "comments.status" ,
345398 Operator = FilterOperator . Eq ,
346399 Value = "pending" ,
400+ IsIncludeFilter = true ,
347401 } ,
348402 } ,
349403 } ,
@@ -373,7 +427,7 @@ public void SeparateIncludeFilters_WithNestedGroups_HandlesCorrectly()
373427 [ Fact ]
374428 public void SeparateIncludeFilters_WithDeepNestedIncludeFilterUsingLeafName_SeparatesCorrectly ( )
375429 {
376- // Arrange - This tests the scenario: include=cve,cve.cvecomments&filter[cvecomments. companyCode][eq]=AA
430+ // Arrange - This tests the scenario: include=cve,cve.cvecomments&filter[cvecomments][ companyCode][eq]=AA
377431 var filters = new FilterGroup
378432 {
379433 Filters = new List < FilterParameter >
@@ -383,6 +437,7 @@ public void SeparateIncludeFilters_WithDeepNestedIncludeFilterUsingLeafName_Sepa
383437 Field = "cvecomments.companyCode" ,
384438 Operator = FilterOperator . Eq ,
385439 Value = "AA" ,
440+ IsIncludeFilter = true , // Bracket syntax: filter[cvecomments][companyCode][eq]=AA
386441 } ,
387442 } ,
388443 } ;
@@ -416,6 +471,7 @@ public void SeparateIncludeFilters_WithDeepNestedIncludeFilterUsingKebabCase_Sep
416471 Field = "cveComments.companyCode" ,
417472 Operator = FilterOperator . Eq ,
418473 Value = "AA" ,
474+ IsIncludeFilter = true , // Bracket syntax: filter[cveComments][companyCode][eq]=AA
419475 } ,
420476 } ,
421477 } ;
@@ -449,12 +505,14 @@ public void SeparateIncludeFilters_WithMultipleDeepNestedFilters_SeparatesCorrec
449505 Field = "author.name" ,
450506 Operator = FilterOperator . Eq ,
451507 Value = "John" ,
508+ IsIncludeFilter = true , // Bracket syntax
452509 } ,
453510 new ( )
454511 {
455512 Field = "comments.status" ,
456513 Operator = FilterOperator . Eq ,
457514 Value = "approved" ,
515+ IsIncludeFilter = true , // Bracket syntax
458516 } ,
459517 } ,
460518 } ;
0 commit comments