Skip to content

Commit 4b523db

Browse files
Merge branch 'main' into dependabot/nuget/JsonApiToolkit/Microsoft.Identity.Abstractions-9.0.0
2 parents af83a77 + e927fea commit 4b523db

16 files changed

Lines changed: 1311 additions & 56 deletions

File tree

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ Documentation is built using DocFX and deployed to GitHub Pages. The documentati
5454

5555
1. **JsonApiController** (`Controllers/JsonApiController.cs`)
5656
- Base controller for JSON:API endpoints
57-
- Provides methods: `JsonApiOk()`, `JsonApiOkAsync()`, `JsonApiCreated()`, `JsonApiNotFound()`, etc.
57+
- Provides methods: `JsonApiOk()`, `JsonApiQueryAsync()`, `JsonApiCreated()`, `JsonApiNotFound()`, etc.
5858
- Handles query parameter parsing and response formatting
5959
- Automatically applies filtering, sorting, pagination, and includes (filtering applies to main entity; includes load related resources)
6060

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
using JsonApiToolkit.Extensions.Querying;
2+
using JsonApiToolkit.Models.Querying.Filtering;
3+
using Microsoft.EntityFrameworkCore;
4+
using Xunit;
5+
6+
namespace JsonApiToolkit.Tests.Extensions;
7+
8+
public class FilteredIncludeBuilderTests
9+
{
10+
[Fact]
11+
public void ApplyFilteredIncludes_WithNoIncludePaths_ReturnsOriginalQuery()
12+
{
13+
// Arrange
14+
var query = CreateMockQueryable<TestEntity>();
15+
List<string>? includePaths = null;
16+
var includeFilters = new List<IncludeFilter>();
17+
18+
// Act
19+
var result = query.ApplyFilteredIncludes(includePaths, includeFilters);
20+
21+
// Assert
22+
Assert.Same(query, result);
23+
}
24+
25+
[Fact]
26+
public void ApplyFilteredIncludes_WithEmptyIncludePaths_ReturnsOriginalQuery()
27+
{
28+
// Arrange
29+
var query = CreateMockQueryable<TestEntity>();
30+
var includePaths = new List<string>();
31+
var includeFilters = new List<IncludeFilter>();
32+
33+
// Act
34+
var result = query.ApplyFilteredIncludes(includePaths, includeFilters);
35+
36+
// Assert
37+
Assert.Same(query, result);
38+
}
39+
40+
[Fact]
41+
public void ApplyFilteredIncludes_WithIncludePathsButNoFilters_AppliesRegularIncludes()
42+
{
43+
// Arrange
44+
var query = CreateMockQueryable<TestEntity>();
45+
var includePaths = new List<string> { "comments", "tags" };
46+
var includeFilters = new List<IncludeFilter>();
47+
48+
// Act
49+
var result = query.ApplyFilteredIncludes(includePaths, includeFilters);
50+
51+
// Assert
52+
// This would normally test that Include was called, but since we're using a mock
53+
// we can't easily verify the EF Core Include calls without a real context
54+
Assert.NotNull(result);
55+
}
56+
57+
[Fact]
58+
public void ApplyFilteredIncludes_WithSimpleIncludeFilter_BuildsCorrectExpression()
59+
{
60+
// Arrange
61+
var query = CreateMockQueryable<TestEntity>();
62+
var includePaths = new List<string> { "comments" };
63+
var includeFilters = new List<IncludeFilter>
64+
{
65+
new()
66+
{
67+
RelationshipPath = "comments",
68+
FieldPath = "status",
69+
Filter = new FilterParameter
70+
{
71+
Field = "status",
72+
Operator = FilterOperator.Eq,
73+
Value = "approved",
74+
},
75+
},
76+
};
77+
78+
// Act
79+
var result = query.ApplyFilteredIncludes(includePaths, includeFilters);
80+
81+
// Assert
82+
Assert.NotNull(result);
83+
// In a real test with EF Core, we would verify the generated SQL contains WHERE clause
84+
}
85+
86+
[Fact]
87+
public void ApplyFilteredIncludes_WithMultipleFiltersOnSameRelationship_CombinesFilters()
88+
{
89+
// Arrange
90+
var query = CreateMockQueryable<TestEntity>();
91+
var includePaths = new List<string> { "comments" };
92+
var includeFilters = new List<IncludeFilter>
93+
{
94+
new()
95+
{
96+
RelationshipPath = "comments",
97+
FieldPath = "status",
98+
Filter = new FilterParameter
99+
{
100+
Field = "status",
101+
Operator = FilterOperator.Eq,
102+
Value = "approved",
103+
},
104+
},
105+
new()
106+
{
107+
RelationshipPath = "comments",
108+
FieldPath = "priority",
109+
Filter = new FilterParameter
110+
{
111+
Field = "priority",
112+
Operator = FilterOperator.Gt,
113+
Value = "5",
114+
},
115+
},
116+
};
117+
118+
// Act
119+
var result = query.ApplyFilteredIncludes(includePaths, includeFilters);
120+
121+
// Assert
122+
Assert.NotNull(result);
123+
}
124+
125+
[Fact]
126+
public void ApplyFilteredIncludes_WithNestedIncludePath_HandlesCorrectly()
127+
{
128+
// Arrange
129+
var query = CreateMockQueryable<TestEntity>();
130+
var includePaths = new List<string> { "comments.author" };
131+
var includeFilters = new List<IncludeFilter>();
132+
133+
// Act
134+
var result = query.ApplyFilteredIncludes(includePaths, includeFilters);
135+
136+
// Assert
137+
Assert.NotNull(result);
138+
}
139+
140+
[Fact]
141+
public void ApplyFilteredIncludes_WithMixedFilteredAndUnfilteredIncludes_HandlesCorrectly()
142+
{
143+
// Arrange
144+
var query = CreateMockQueryable<TestEntity>();
145+
var includePaths = new List<string> { "comments", "tags", "author" };
146+
var includeFilters = new List<IncludeFilter>
147+
{
148+
new()
149+
{
150+
RelationshipPath = "comments",
151+
FieldPath = "status",
152+
Filter = new FilterParameter
153+
{
154+
Field = "status",
155+
Operator = FilterOperator.Eq,
156+
Value = "approved",
157+
},
158+
},
159+
// tags and author have no filters
160+
};
161+
162+
// Act
163+
var result = query.ApplyFilteredIncludes(includePaths, includeFilters);
164+
165+
// Assert
166+
Assert.NotNull(result);
167+
}
168+
169+
private static IQueryable<T> CreateMockQueryable<T>()
170+
where T : class
171+
{
172+
// Create a minimal mock queryable for testing
173+
// In a real scenario, this would be an EF Core DbSet or similar
174+
var data = new List<T>().AsQueryable();
175+
return data;
176+
}
177+
178+
// Test entity classes for testing
179+
public class TestEntity
180+
{
181+
public int Id { get; set; }
182+
public string Title { get; set; } = string.Empty;
183+
public string Status { get; set; } = string.Empty;
184+
public List<Comment> Comments { get; set; } = new();
185+
public List<Tag> Tags { get; set; } = new();
186+
public Author? Author { get; set; }
187+
}
188+
189+
public class Comment
190+
{
191+
public int Id { get; set; }
192+
public string Content { get; set; } = string.Empty;
193+
public string Status { get; set; } = string.Empty;
194+
public int Priority { get; set; }
195+
public Author? Author { get; set; }
196+
public string? CompanyCode { get; set; }
197+
}
198+
199+
public class Tag
200+
{
201+
public int Id { get; set; }
202+
public string Name { get; set; } = string.Empty;
203+
}
204+
205+
public class Author
206+
{
207+
public int Id { get; set; }
208+
public string Name { get; set; } = string.Empty;
209+
public string Department { get; set; } = string.Empty;
210+
public string Role { get; set; } = string.Empty;
211+
}
212+
}

0 commit comments

Comments
 (0)