Skip to content

Commit d3c5bee

Browse files
committed
test(queryParser): add test coverage and handle exception
1 parent bd98085 commit d3c5bee

File tree

2 files changed

+165
-18
lines changed

2 files changed

+165
-18
lines changed

src/JsonApiDotNetCore/Services/QueryParser.cs

+36-17
Original file line numberDiff line numberDiff line change
@@ -112,38 +112,47 @@ protected virtual(string operation, string value) ParseFilterOperation(string va
112112
return (prefix, value);
113113
}
114114

115-
protected virtual PageQuery ParsePageQuery(PageQuery pageQuery, string key, string value) {
115+
protected virtual PageQuery ParsePageQuery(PageQuery pageQuery, string key, string value)
116+
{
116117
// expected input = page[size]=10
117118
// page[number]=1
118119
pageQuery = pageQuery ?? new PageQuery();
119120

120-
var propertyName = key.Split(OPEN_BRACKET, CLOSE_BRACKET) [1];
121+
var propertyName = key.Split(OPEN_BRACKET, CLOSE_BRACKET)[1];
121122

122123
const string SIZE = "size";
123124
const string NUMBER = "number";
124125

125126
if (propertyName == SIZE)
126-
pageQuery.PageSize = Convert.ToInt32(value);
127+
pageQuery.PageSize = int.TryParse(value, out var pageSize) ?
128+
pageSize :
129+
throw new JsonApiException(400, $"Invalid page size '{value}'");
130+
127131
else if (propertyName == NUMBER)
128-
pageQuery.PageOffset = Convert.ToInt32(value);
132+
pageQuery.PageOffset = int.TryParse(value, out var pageOffset) ?
133+
pageOffset :
134+
throw new JsonApiException(400, $"Invalid page size '{value}'");
129135

130136
return pageQuery;
131137
}
132138

133139
// sort=id,name
134140
// sort=-id
135-
protected virtual List<SortQuery> ParseSortParameters(string value) {
141+
protected virtual List<SortQuery> ParseSortParameters(string value)
142+
{
136143
var sortParameters = new List<SortQuery>();
137144

138145
const char DESCENDING_SORT_OPERATOR = '-';
139146
var sortSegments = value.Split(COMMA);
140147

141-
foreach (var sortSegment in sortSegments) {
148+
foreach (var sortSegment in sortSegments)
149+
{
142150

143151
var propertyName = sortSegment;
144152
var direction = SortDirection.Ascending;
145153

146-
if (sortSegment[0] == DESCENDING_SORT_OPERATOR) {
154+
if (sortSegment[0] == DESCENDING_SORT_OPERATOR)
155+
{
147156
direction = SortDirection.Descending;
148157
propertyName = propertyName.Substring(1);
149158
}
@@ -166,37 +175,47 @@ protected virtual List<string> ParseIncludedRelationships(string value) {
166175
.ToList();
167176
}
168177

169-
protected virtual List<string> ParseFieldsQuery(string key, string value) {
178+
protected virtual List<string> ParseFieldsQuery(string key, string value)
179+
{
170180
// expected: fields[TYPE]=prop1,prop2
171-
var typeName = key.Split(OPEN_BRACKET, CLOSE_BRACKET) [1];
181+
var typeName = key.Split(OPEN_BRACKET, CLOSE_BRACKET)[1];
172182

173183
const string ID = "Id";
174184
var includedFields = new List<string> { ID };
175185

176-
if (typeName != _controllerContext.RequestEntity.EntityName)
186+
// this will not support nested inclusions, it requires that the typeName is the current request type
187+
if (string.Equals(typeName, _controllerContext.RequestEntity.EntityName, StringComparison.OrdinalIgnoreCase) == false)
177188
return includedFields;
178189

179190
var fields = value.Split(COMMA);
180-
foreach (var field in fields) {
181-
var internalAttrName = _controllerContext.RequestEntity
191+
foreach (var field in fields)
192+
{
193+
var attr = _controllerContext.RequestEntity
182194
.Attributes
183-
.SingleOrDefault(attr => attr.PublicAttributeName == field)
184-
.InternalAttributeName;
195+
.SingleOrDefault(a => string.Equals(a.PublicAttributeName, field, StringComparison.OrdinalIgnoreCase));
196+
197+
if (attr == null) throw new JsonApiException(400, $"'{_controllerContext.RequestEntity.EntityName}' does not contain '{field}'.");
198+
199+
var internalAttrName = attr.InternalAttributeName;
185200
includedFields.Add(internalAttrName);
186201
}
187202

188203
return includedFields;
189204
}
190205

191-
protected virtual AttrAttribute GetAttribute(string propertyName) {
192-
try {
206+
protected virtual AttrAttribute GetAttribute(string propertyName)
207+
{
208+
try
209+
{
193210
return _controllerContext
194211
.RequestEntity
195212
.Attributes
196213
.Single(attr =>
197214
string.Equals(attr.PublicAttributeName, propertyName, StringComparison.OrdinalIgnoreCase)
198215
);
199-
} catch (InvalidOperationException e) {
216+
}
217+
catch (InvalidOperationException e)
218+
{
200219
throw new JsonApiException(400, $"Attribute '{propertyName}' does not exist on resource '{_controllerContext.RequestEntity.EntityName}'", e);
201220
}
202221
}

test/UnitTests/Services/QueryParser_Tests.cs

+129-1
Original file line numberDiff line numberDiff line change
@@ -224,5 +224,133 @@ public void Can_Disable_Fields()
224224
// assert
225225
Assert.Empty(querySet.Fields);
226226
}
227+
228+
[Fact]
229+
public void Can_Parse_Fields_Query()
230+
{
231+
// arrange
232+
const string type = "articles";
233+
const string attrName = "some-field";
234+
const string internalAttrName = "SomeField";
235+
236+
var query = new Dictionary<string, StringValues> { { $"fields[{type}]", new StringValues(attrName) } };
237+
238+
_queryCollectionMock
239+
.Setup(m => m.GetEnumerator())
240+
.Returns(query.GetEnumerator());
241+
242+
_controllerContextMock
243+
.Setup(m => m.RequestEntity)
244+
.Returns(new ContextEntity
245+
{
246+
EntityName = type,
247+
Attributes = new List<AttrAttribute>
248+
{
249+
new AttrAttribute(attrName)
250+
{
251+
InternalAttributeName = internalAttrName
252+
}
253+
}
254+
});
255+
256+
var queryParser = new QueryParser(_controllerContextMock.Object, new JsonApiOptions());
257+
258+
// act
259+
var querySet = queryParser.Parse(_queryCollectionMock.Object);
260+
261+
// assert
262+
Assert.NotEmpty(querySet.Fields);
263+
Assert.Equal(2, querySet.Fields.Count);
264+
Assert.Equal("Id", querySet.Fields[0]);
265+
Assert.Equal(internalAttrName, querySet.Fields[1]);
266+
}
267+
268+
[Fact]
269+
public void Throws_JsonApiException_If_Field_DoesNotExist()
270+
{
271+
// arrange
272+
const string type = "articles";
273+
const string attrName = "dne";
274+
275+
var query = new Dictionary<string, StringValues> { { $"fields[{type}]", new StringValues(attrName) } };
276+
277+
_queryCollectionMock
278+
.Setup(m => m.GetEnumerator())
279+
.Returns(query.GetEnumerator());
280+
281+
_controllerContextMock
282+
.Setup(m => m.RequestEntity)
283+
.Returns(new ContextEntity
284+
{
285+
EntityName = type,
286+
Attributes = new List<AttrAttribute>()
287+
});
288+
289+
var queryParser = new QueryParser(_controllerContextMock.Object, new JsonApiOptions());
290+
291+
// act , assert
292+
var ex = Assert.Throws<JsonApiException>(() => queryParser.Parse(_queryCollectionMock.Object));
293+
Assert.Equal(400, ex.GetStatusCode());
294+
}
295+
296+
[Theory]
297+
[InlineData("1", 1, false)]
298+
[InlineData("abcde", 0, true)]
299+
[InlineData("", 0, true)]
300+
public void Can_Parse_Page_Size_Query(string value, int expectedValue, bool shouldThrow)
301+
{
302+
// arrange
303+
var query = new Dictionary<string, StringValues>
304+
{ { "page[size]", new StringValues(value) }
305+
};
306+
307+
_queryCollectionMock
308+
.Setup(m => m.GetEnumerator())
309+
.Returns(query.GetEnumerator());
310+
311+
var queryParser = new QueryParser(_controllerContextMock.Object, new JsonApiOptions());
312+
313+
// act
314+
if (shouldThrow)
315+
{
316+
var ex = Assert.Throws<JsonApiException>(() => queryParser.Parse(_queryCollectionMock.Object));
317+
Assert.Equal(400, ex.GetStatusCode());
318+
}
319+
else
320+
{
321+
var querySet = queryParser.Parse(_queryCollectionMock.Object);
322+
Assert.Equal(expectedValue, querySet.PageQuery.PageSize);
323+
}
324+
}
325+
326+
[Theory]
327+
[InlineData("1", 1, false)]
328+
[InlineData("abcde", 0, true)]
329+
[InlineData("", 0, true)]
330+
public void Can_Parse_Page_Number_Query(string value, int expectedValue, bool shouldThrow)
331+
{
332+
// arrange
333+
var query = new Dictionary<string, StringValues>
334+
{ { "page[number]", new StringValues(value) }
335+
};
336+
337+
_queryCollectionMock
338+
.Setup(m => m.GetEnumerator())
339+
.Returns(query.GetEnumerator());
340+
341+
var queryParser = new QueryParser(_controllerContextMock.Object, new JsonApiOptions());
342+
343+
// act
344+
if (shouldThrow)
345+
{
346+
var ex = Assert.Throws<JsonApiException>(() => queryParser.Parse(_queryCollectionMock.Object));
347+
Assert.Equal(400, ex.GetStatusCode());
348+
}
349+
else
350+
{
351+
var querySet = queryParser.Parse(_queryCollectionMock.Object);
352+
Assert.Equal(expectedValue, querySet.PageQuery.PageOffset);
353+
}
354+
}
227355
}
228-
}
356+
}

0 commit comments

Comments
 (0)