Skip to content

Spec tests #30

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Feb 20, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,16 @@ identifier):
?filter[attribute]=ge:value
```


## Sorting

Resources can be sorted by an attribute:

```
?sort=attribute // ascending
?sort=-attribute // descending
```

# Tests

## Running
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using JsonApiDotNetCore.Middleware;
using Microsoft.AspNetCore.Builder;

namespace JsonApiDotNetCore.Routing
Expand All @@ -6,8 +7,10 @@ public static class IApplicationBuilderExtensions
{
public static IApplicationBuilder UseJsonApi(this IApplicationBuilder app)
{
app.UseMiddleware<RequestMiddleware>();

app.UseMvc();

return app;
}
}
Expand Down
41 changes: 29 additions & 12 deletions src/JsonApiDotNetCore/Formatters/JsonApiOutputFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,38 +34,55 @@ public async Task WriteAsync(OutputFormatterWriteContext context)
logger?.LogInformation("Formatting response as JSONAPI");

var response = context.HttpContext.Response;

using (var writer = context.WriterFactory(response.Body, Encoding.UTF8))
{
var jsonApiContext = GetService<IJsonApiContext>(context);

response.ContentType = "application/vnd.api+json";
string responseContent;
try
{
if(context.Object.GetType() == typeof(Error) || jsonApiContext.RequestEntity == null)
{
logger?.LogInformation("Response was not a JSONAPI entity. Serializing as plain JSON.");

responseContent = JsonConvert.SerializeObject(context.Object);
}
else
responseContent = JsonApiSerializer.Serialize(context.Object, jsonApiContext);
responseContent = GetResponseBody(context.Object, jsonApiContext, logger);
}
catch(Exception e)
catch (Exception e)
{
logger?.LogError(new EventId(), e, "An error ocurred while formatting the response");
responseContent = new Error("400", e.Message).GetJson();
var errors = new ErrorCollection();
errors.Add(new Error("400", e.Message));
responseContent = errors.GetJson();
response.StatusCode = 400;
}

await writer.WriteAsync(responseContent);
await writer.FlushAsync();
await writer.FlushAsync();
}
}

private T GetService<T>(OutputFormatterWriteContext context)
{
return context.HttpContext.RequestServices.GetService<T>();
}

private string GetResponseBody(object responseObject, IJsonApiContext jsonApiContext, ILogger logger)
{
if (responseObject.GetType() == typeof(Error) || jsonApiContext.RequestEntity == null)
{
if (responseObject.GetType() == typeof(Error))
{
var errors = new ErrorCollection();
errors.Add((Error)responseObject);
return errors.GetJson();
}
else
{
logger?.LogInformation("Response was not a JSONAPI entity. Serializing as plain JSON.");
return JsonConvert.SerializeObject(responseObject);
}
}
else
{
return JsonApiSerializer.Serialize(responseObject, jsonApiContext);
}
}
}
}
10 changes: 3 additions & 7 deletions src/JsonApiDotNetCore/Internal/Error.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ namespace JsonApiDotNetCore.Internal
{
public class Error
{
public Error()
{ }

public Error(string status, string title)
{
Status = status;
Expand All @@ -25,12 +28,5 @@ public Error(string status, string title, string detail)

[JsonProperty("status")]
public string Status { get; set; }

public string GetJson()
{
return JsonConvert.SerializeObject(this, new JsonSerializerSettings {
NullValueHandling = NullValueHandling.Ignore
});
}
}
}
27 changes: 27 additions & 0 deletions src/JsonApiDotNetCore/Internal/ErrorCollection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System.Collections.Generic;
using Newtonsoft.Json;

namespace JsonApiDotNetCore.Internal
{
public class ErrorCollection
{
public ErrorCollection()
{
Errors = new List<Error>();
}

public List<Error> Errors { get; set; }

public void Add(Error error)
{
Errors.Add(error);
}

public string GetJson()
{
return JsonConvert.SerializeObject(this, new JsonSerializerSettings {
NullValueHandling = NullValueHandling.Ignore
});
}
}
}
4 changes: 4 additions & 0 deletions src/JsonApiDotNetCore/Internal/Query/QuerySet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,16 @@ private void BuildQuerySet(IQueryCollection query)
if (pair.Key.StartsWith("include"))
{
IncludedRelationships = ParseIncludedRelationships(pair.Value);
continue;
}

if (pair.Key.StartsWith("page"))
{
PageQuery = ParsePageQuery(pair.Key, pair.Value);
continue;
}

throw new JsonApiException("400", $"{pair} is not a valid query.");
}
}

Expand Down
67 changes: 67 additions & 0 deletions src/JsonApiDotNetCore/Middleware/RequestMiddleware.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;

namespace JsonApiDotNetCore.Middleware
{
public class RequestMiddleware
{
private readonly RequestDelegate _next;

public RequestMiddleware(RequestDelegate next)
{
_next = next;
}

public async Task Invoke(HttpContext context)
{
if (IsValid(context))
await _next(context);
}

private static bool IsValid(HttpContext context)
{
return IsValidContentTypeHeader(context) && IsValidAcceptHeader(context);
}

private static bool IsValidContentTypeHeader(HttpContext context)
{
var contentType = context.Request.ContentType;
if (contentType != null && ContainsMediaTypeParameters(contentType))
{
FlushResponse(context, 415);
return false;
}
return true;
}

private static bool IsValidAcceptHeader(HttpContext context)
{
var acceptHeaders = new StringValues();
if (context.Request.Headers.TryGetValue("Accept", out acceptHeaders))
{
foreach (var acceptHeader in acceptHeaders)
{
if (ContainsMediaTypeParameters(acceptHeader))
{
FlushResponse(context, 406);
return false;
}
}
}
return true;
}

private static bool ContainsMediaTypeParameters(string mediaType)
{
var mediaTypeArr = mediaType.Split(';');
return (mediaTypeArr[0] == "application/vnd.api+json" && mediaTypeArr.Length == 2);
}

private static void FlushResponse(HttpContext context, int statusCode)
{
context.Response.StatusCode = statusCode;
context.Response.Body.Flush();
}
}
}
2 changes: 1 addition & 1 deletion src/JsonApiDotNetCore/project.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "0.2.8",
"version": "0.2.9",

"dependencies": {
"Microsoft.NETCore.App": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using DotNetCoreDocs;
using DotNetCoreDocs.Models;
using DotNetCoreDocs.Writers;
using JsonApiDotNetCoreExample;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Xunit;

namespace JsonApiDotNetCoreExampleTests.Acceptance.Spec
{
[Collection("WebHostCollection")]
public class ContentNegotiation
{
private DocsFixture<Startup, JsonDocWriter> _fixture;
public ContentNegotiation(DocsFixture<Startup, JsonDocWriter> fixture)
{
_fixture = fixture;
}

[Fact]
public async Task Server_Sends_Correct_ContentType_Header()
{
// arrange
var builder = new WebHostBuilder()
.UseStartup<Startup>();
var httpMethod = new HttpMethod("GET");
var route = "/api/v1/todo-items";
var description = new RequestProperties("Server Sends Correct Content Type Header");
var server = new TestServer(builder);
var client = server.CreateClient();
var request = new HttpRequestMessage(httpMethod, route);

// act
var response = await client.SendAsync(request);

// assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal("application/vnd.api+json", response.Content.Headers.ContentType.ToString());
}

[Fact]
public async Task Server_Responds_415_With_MediaType_Parameters()
{
// arrange
var builder = new WebHostBuilder()
.UseStartup<Startup>();
var httpMethod = new HttpMethod("GET");
var route = "/api/v1/todo-items";
var description = new RequestProperties("Server responds with 415 if request contains media type parameters");
var server = new TestServer(builder);
var client = server.CreateClient();
var request = new HttpRequestMessage(httpMethod, route);
request.Content = new StringContent(string.Empty);
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/vnd.api+json");
request.Content.Headers.ContentType.CharSet = "ISO-8859-4";

// act
var response = await client.SendAsync(request);

// assert
Assert.Equal(HttpStatusCode.UnsupportedMediaType, response.StatusCode);
}

[Fact]
public async Task ServerResponds_406_If_RequestAcceptHeader_Contains_MediaTypeParameters()
{
// arrange
var builder = new WebHostBuilder()
.UseStartup<Startup>();
var httpMethod = new HttpMethod("GET");
var route = "/api/v1/todo-items";
var description = new RequestProperties("Server responds with 406...");
var server = new TestServer(builder);
var client = server.CreateClient();
var acceptHeader = new MediaTypeWithQualityHeaderValue("application/vnd.api+json");
acceptHeader.CharSet = "ISO-8859-4";
client.DefaultRequestHeaders
.Accept
.Add(acceptHeader);
var request = new HttpRequestMessage(httpMethod, route);

// act
var response = await client.SendAsync(request);

// assert
Assert.Equal(HttpStatusCode.NotAcceptable, response.StatusCode);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using DotNetCoreDocs;
using DotNetCoreDocs.Models;
using DotNetCoreDocs.Writers;
using JsonApiDotNetCoreExample;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Newtonsoft.Json;
using Xunit;
using JsonApiDotNetCore.Internal;

namespace JsonApiDotNetCoreExampleTests.Acceptance.Spec
{
[Collection("WebHostCollection")]
public class QueryParameters
{
private DocsFixture<Startup, JsonDocWriter> _fixture;
public QueryParameters(DocsFixture<Startup, JsonDocWriter> fixture)
{
_fixture = fixture;
}

[Fact]
public async Task Server_Returns_400_ForUnknownQueryParam()
{
// arrange
const string queryKey = "unknownKey";
const string queryValue = "value";
var builder = new WebHostBuilder()
.UseStartup<Startup>();
var httpMethod = new HttpMethod("GET");
var route = $"/api/v1/todo-items?{queryKey}={queryValue}";
var description = new RequestProperties("Server Returns 400 For Unknown Query Params");
var server = new TestServer(builder);
var client = server.CreateClient();
var request = new HttpRequestMessage(httpMethod, route);

// act
var response = await client.SendAsync(request);
var body = JsonConvert.DeserializeObject<ErrorCollection>(await response.Content.ReadAsStringAsync());

// assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
Assert.Equal(1, body.Errors.Count);
Assert.Equal($"[{queryKey}, {queryValue}] is not a valid query.", body.Errors[0].Title);
}
}
}
Loading