Skip to content

Fix/#340: Operations issues #357

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 2 commits into from
Jul 27, 2018
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
1 change: 0 additions & 1 deletion src/JsonApiDotNetCore/Builders/DocumentBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,6 @@ public DocumentData GetData(ContextEntity contextEntity, IIdentifiable entity, I

return data;
}

private bool ShouldIncludeAttribute(AttrAttribute attr, object attributeValue)
{
return OmitNullValuedAttribute(attr, attributeValue) == false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ public IOpProcessor LocateCreateService(Operation operation)
{
var resource = operation.GetResourceTypeName();

var contextEntity = _context.ContextGraph.GetContextEntity(resource);
var contextEntity = GetResourceMetadata(resource);

var processor = _processorFactory.GetProcessor<IOpProcessor>(
typeof(ICreateOpProcessor<,>), contextEntity.EntityType, contextEntity.IdentityType
);
Expand All @@ -64,7 +65,8 @@ public IOpProcessor LocateGetService(Operation operation)
{
var resource = operation.GetResourceTypeName();

var contextEntity = _context.ContextGraph.GetContextEntity(resource);
var contextEntity = GetResourceMetadata(resource);

var processor = _processorFactory.GetProcessor<IOpProcessor>(
typeof(IGetOpProcessor<,>), contextEntity.EntityType, contextEntity.IdentityType
);
Expand All @@ -77,7 +79,8 @@ public IOpProcessor LocateRemoveService(Operation operation)
{
var resource = operation.GetResourceTypeName();

var contextEntity = _context.ContextGraph.GetContextEntity(resource);
var contextEntity = GetResourceMetadata(resource);

var processor = _processorFactory.GetProcessor<IOpProcessor>(
typeof(IRemoveOpProcessor<,>), contextEntity.EntityType, contextEntity.IdentityType
);
Expand All @@ -90,15 +93,22 @@ public IOpProcessor LocateUpdateService(Operation operation)
{
var resource = operation.GetResourceTypeName();

var contextEntity = _context.ContextGraph.GetContextEntity(resource);
if (contextEntity == null)
throw new JsonApiException(400, $"This API does not expose a resource of type '{resource}'.");
var contextEntity = GetResourceMetadata(resource);

var processor = _processorFactory.GetProcessor<IOpProcessor>(
typeof(IUpdateOpProcessor<,>), contextEntity.EntityType, contextEntity.IdentityType
);

return processor;
}

private ContextEntity GetResourceMetadata(string resourceName)
{
var contextEntity = _context.ContextGraph.GetContextEntity(resourceName);
if(contextEntity == null)
throw new JsonApiException(400, $"This API does not expose a resource of type '{resourceName}'.");

return contextEntity;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
Expand Down Expand Up @@ -83,10 +84,10 @@ public async Task<Operation> ProcessAsync(Operation operation)
};

operationResult.Data = string.IsNullOrWhiteSpace(operation.Ref.Id)
? await GetAllAsync(operation)
: string.IsNullOrWhiteSpace(operation.Ref.Relationship)
? await GetByIdAsync(operation)
: await GetRelationshipAsync(operation);
? await GetAllAsync(operation)
: string.IsNullOrWhiteSpace(operation.Ref.Relationship)
? await GetByIdAsync(operation)
: await GetRelationshipAsync(operation);

return operationResult;
}
Expand Down Expand Up @@ -135,11 +136,38 @@ private async Task<object> GetRelationshipAsync(Operation operation)
// when no generic parameter is available
var relationshipType = _contextGraph.GetContextEntity(operation.GetResourceTypeName())
.Relationships.Single(r => r.Is(operation.Ref.Relationship)).Type;

var relatedContextEntity = _jsonApiContext.ContextGraph.GetContextEntity(relationshipType);

var doc = _documentBuilder.GetData(relatedContextEntity, result as IIdentifiable); // TODO: if this is safe, then it should be cast in the GetRelationshipAsync call
if (result == null)
return null;

if (result is IIdentifiable singleResource)
return GetData(relatedContextEntity, singleResource);

return doc;
if (result is IEnumerable multipleResults)
return GetData(relatedContextEntity, multipleResults);

throw new JsonApiException(500,
$"An unexpected type was returned from '{_getRelationship.GetType()}.{nameof(IGetRelationshipService<T, TId>.GetRelationshipAsync)}'.",
detail: $"Type '{result.GetType()} does not implement {nameof(IIdentifiable)} nor {nameof(IEnumerable<IIdentifiable>)}'");
}

private DocumentData GetData(ContextEntity contextEntity, IIdentifiable singleResource)
{
return _documentBuilder.GetData(contextEntity, singleResource);
}

private List<DocumentData> GetData(ContextEntity contextEntity, IEnumerable multipleResults)
{
var resources = new List<DocumentData>();
foreach (var singleResult in multipleResults)
{
if (singleResult is IIdentifiable resource)
resources.Add(_documentBuilder.GetData(contextEntity, resource));
}

return resources;
}

private TId GetReferenceId(Operation operation) => TypeHelper.ConvertType<TId>(operation.Ref.Id);
Expand Down
37 changes: 36 additions & 1 deletion test/OperationsExampleTests/Get/GetRelationshipTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class GetRelationshipTests : Fixture, IDisposable
private readonly Faker _faker = new Faker();

[Fact]
public async Task Can_Get_Article_Author()
public async Task Can_Get_HasOne_Relationship()
{
// arrange
var context = GetService<AppDbContext>();
Expand Down Expand Up @@ -48,5 +48,40 @@ public async Task Can_Get_Article_Author()
Assert.Equal(author.Id.ToString(), resourceObject.Id);
Assert.Equal("authors", resourceObject.Type);
}

[Fact]
public async Task Can_Get_HasMany_Relationship()
{
// arrange
var context = GetService<AppDbContext>();
var author = AuthorFactory.Get();
var article = ArticleFactory.Get();
article.Author = author;
context.Articles.Add(article);
context.SaveChanges();

var content = new
{
operations = new[] {
new Dictionary<string, object> {
{ "op", "get"},
{ "ref", new { type = "authors", id = author.StringId, relationship = "articles" } }
}
}
};

// act
var (response, data) = await PatchAsync<OperationsDocument>("api/bulk", content);

// assert
Assert.NotNull(response);
Assert.NotNull(data);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Single(data.Operations);

var resourceObject = data.Operations.Single().DataList.Single();
Assert.Equal(article.Id.ToString(), resourceObject.Id);
Assert.Equal("articles", resourceObject.Type);
}
}
}
118 changes: 70 additions & 48 deletions test/OperationsExampleTests/Get/GetTests.cs
Original file line number Diff line number Diff line change
@@ -1,51 +1,73 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Bogus;
using JsonApiDotNetCore.Models.Operations;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Bogus;
using JsonApiDotNetCore.Models.Operations;
using JsonApiDotNetCoreExample.Data;
using OperationsExampleTests.Factories;
using Xunit;
namespace OperationsExampleTests
{
public class GetByIdTests : Fixture, IDisposable
{
using OperationsExampleTests.Factories;
using Xunit;

namespace OperationsExampleTests
{
public class GetByIdTests : Fixture, IDisposable
{
private readonly Faker _faker = new Faker();
[Fact]
public async Task Can_Get_Authors()
{
// arrange
var expectedCount = _faker.Random.Int(1, 10);
var context = GetService<AppDbContext>();
context.Articles.RemoveRange(context.Articles);
context.Authors.RemoveRange(context.Authors);
var authors = AuthorFactory.Get(expectedCount);
context.AddRange(authors);
context.SaveChanges();
var content = new
{
operations = new[] {
new Dictionary<string, object> {
{ "op", "get"},
{ "ref", new { type = "authors" } }
}
}
};
// act
var result = await PatchAsync<OperationsDocument>("api/bulk", content);
// assert
Assert.NotNull(result.response);
Assert.NotNull(result.data);
Assert.Equal(HttpStatusCode.OK, result.response.StatusCode);
Assert.Single(result.data.Operations);
Assert.Equal(expectedCount, result.data.Operations.Single().DataList.Count);

[Fact]
public async Task Can_Get_Authors()
{
// arrange
var expectedCount = _faker.Random.Int(1, 10);
var context = GetService<AppDbContext>();
context.Articles.RemoveRange(context.Articles);
context.Authors.RemoveRange(context.Authors);
var authors = AuthorFactory.Get(expectedCount);
context.AddRange(authors);
context.SaveChanges();

var content = new
{
operations = new[] {
new Dictionary<string, object> {
{ "op", "get"},
{ "ref", new { type = "authors" } }
}
}
};

// act
var result = await PatchAsync<OperationsDocument>("api/bulk", content);

// assert
Assert.NotNull(result.response);
Assert.NotNull(result.data);
Assert.Equal(HttpStatusCode.OK, result.response.StatusCode);
Assert.Single(result.data.Operations);
Assert.Equal(expectedCount, result.data.Operations.Single().DataList.Count);
}
}
}

[Fact]
public async Task Get_Non_Existent_Type_Returns_400()
{
// arrange
var content = new
{
operations = new[] {
new Dictionary<string, object> {
{ "op", "get"},
{ "ref", new { type = "non-existent-type" } }
}
}
};

// act
var result = await PatchAsync<OperationsDocument>("api/bulk", content);

// assert
Assert.Equal(HttpStatusCode.BadRequest, result.response.StatusCode);
}
}
}
102 changes: 102 additions & 0 deletions test/UnitTests/Services/Operations/OperationsProcessorResolverTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
using JsonApiDotNetCore.Builders;
using JsonApiDotNetCore.Internal;
using JsonApiDotNetCore.Internal.Generics;
using JsonApiDotNetCore.Models.Operations;
using JsonApiDotNetCore.Services;
using JsonApiDotNetCore.Services.Operations;
using Moq;
using Xunit;

namespace UnitTests.Services
{
public class OperationProcessorResolverTests
{
private readonly Mock<IGenericProcessorFactory> _processorFactoryMock;
public readonly Mock<IJsonApiContext> _jsonApiContextMock;

public OperationProcessorResolverTests()
{
_processorFactoryMock = new Mock<IGenericProcessorFactory>();
_jsonApiContextMock = new Mock<IJsonApiContext>();
}

[Fact]
public void LocateCreateService_Throws_400_For_Entity_Not_Registered()
{
// arrange
_jsonApiContextMock.Setup(m => m.ContextGraph).Returns(new ContextGraphBuilder().Build());
var service = GetService();
var op = new Operation
{
Ref = new ResourceReference
{
Type = "non-existent-type"
}
};

// act, assert
var e = Assert.Throws<JsonApiException>(() => service.LocateCreateService(op));
Assert.Equal(400, e.GetStatusCode());
}

[Fact]
public void LocateGetService_Throws_400_For_Entity_Not_Registered()
{
// arrange
_jsonApiContextMock.Setup(m => m.ContextGraph).Returns(new ContextGraphBuilder().Build());
var service = GetService();
var op = new Operation
{
Ref = new ResourceReference
{
Type = "non-existent-type"
}
};

// act, assert
var e = Assert.Throws<JsonApiException>(() => service.LocateGetService(op));
Assert.Equal(400, e.GetStatusCode());
}

[Fact]
public void LocateRemoveService_Throws_400_For_Entity_Not_Registered()
{
// arrange
_jsonApiContextMock.Setup(m => m.ContextGraph).Returns(new ContextGraphBuilder().Build());
var service = GetService();
var op = new Operation
{
Ref = new ResourceReference
{
Type = "non-existent-type"
}
};

// act, assert
var e = Assert.Throws<JsonApiException>(() => service.LocateRemoveService(op));
Assert.Equal(400, e.GetStatusCode());
}

[Fact]
public void LocateUpdateService_Throws_400_For_Entity_Not_Registered()
{
// arrange
_jsonApiContextMock.Setup(m => m.ContextGraph).Returns(new ContextGraphBuilder().Build());
var service = GetService();
var op = new Operation
{
Ref = new ResourceReference
{
Type = "non-existent-type"
}
};

// act, assert
var e = Assert.Throws<JsonApiException>(() => service.LocateUpdateService(op));
Assert.Equal(400, e.GetStatusCode());
}

private OperationProcessorResolver GetService()
=> new OperationProcessorResolver(_processorFactoryMock.Object, _jsonApiContextMock.Object);
}
}