From 264920339cfe44b598147e31bc270aeef8fc6141 Mon Sep 17 00:00:00 2001 From: maurei Date: Fri, 18 Sep 2020 14:11:44 +0200 Subject: [PATCH 01/20] feat: resource inheritance compatabiliy --- .../Controllers/KebabCasedModelsController.cs | 18 ------------- .../EntityFrameworkCoreRepository.cs | 7 ++++-- .../Serialization/BaseDeserializer.cs | 25 +++++++++++++------ .../IntegrationTests/SoftDeletion/Company.cs | 13 +++++----- 4 files changed, 28 insertions(+), 35 deletions(-) delete mode 100644 src/Examples/JsonApiDotNetCoreExample/Controllers/KebabCasedModelsController.cs diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/KebabCasedModelsController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/KebabCasedModelsController.cs deleted file mode 100644 index a473241247..0000000000 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/KebabCasedModelsController.cs +++ /dev/null @@ -1,18 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Services; -using JsonApiDotNetCoreExample.Models; -using Microsoft.Extensions.Logging; - -namespace JsonApiDotNetCoreExample.Controllers -{ - public sealed class KebabCasedModelsController : JsonApiController - { - public KebabCasedModelsController( - IJsonApiOptions options, - ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { } - } -} diff --git a/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs b/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs index 81823efdff..6803f8a8ea 100644 --- a/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs +++ b/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs @@ -264,15 +264,18 @@ private IEnumerable GetTrackedManyRelationshipValue(IEnumerable r { if (relationshipValues == null) return null; bool newWasAlreadyAttached = false; + var trackedPointerCollection = TypeHelper.CopyToTypedCollection(relationshipValues.Select(pointer => { - // convert each element in the value list to relationshipAttr.DependentType. var tracked = AttachOrGetTracked(pointer); if (tracked != null) newWasAlreadyAttached = true; - return Convert.ChangeType(tracked ?? pointer, relationshipAttr.RightType); + + // we should recalculate the target type for every iteration because types may vary. This is possible with resource inheritance + return Convert.ChangeType(tracked ?? pointer, (tracked ?? pointer).GetType()); }), relationshipAttr.Property.PropertyType); if (newWasAlreadyAttached) wasAlreadyAttached = true; + return trackedPointerCollection; } diff --git a/src/JsonApiDotNetCore/Serialization/BaseDeserializer.cs b/src/JsonApiDotNetCore/Serialization/BaseDeserializer.cs index cbe4b793c8..c7cecc7226 100644 --- a/src/JsonApiDotNetCore/Serialization/BaseDeserializer.cs +++ b/src/JsonApiDotNetCore/Serialization/BaseDeserializer.cs @@ -106,7 +106,8 @@ protected virtual IIdentifiable SetRelationships(IIdentifiable resource, IDictio var resourceProperties = resource.GetType().GetProperties(); foreach (var attr in relationshipAttributes) { - if (!relationshipValues.TryGetValue(attr.PublicName, out RelationshipEntry relationshipData) || !relationshipData.IsPopulated) + var relationshipIsProvided = relationshipValues.TryGetValue(attr.PublicName, out RelationshipEntry relationshipData); + if (!relationshipIsProvided || !relationshipData.IsPopulated) continue; if (attr is HasOneAttribute hasOneAttribute) @@ -168,15 +169,19 @@ private void SetHasOneRelationship(IIdentifiable resource, var rio = (ResourceIdentifierObject)relationshipData.Data; var relatedId = rio?.Id; + var relatedResourceType = relationshipData.SingleData == null + ? attr.RightType + : ResourceContextProvider.GetResourceContext(relationshipData.SingleData.Type).ResourceType; + // this does not make sense in the following case: if we're setting the dependent of a one-to-one relationship, IdentifiablePropertyName should be null. var foreignKeyProperty = resourceProperties.FirstOrDefault(p => p.Name == attr.IdentifiablePropertyName); if (foreignKeyProperty != null) // there is a FK from the current resource pointing to the related object, // i.e. we're populating the relationship from the dependent side. - SetForeignKey(resource, foreignKeyProperty, attr, relatedId); + SetForeignKey(resource, foreignKeyProperty, attr, relatedId, relatedResourceType); - SetNavigation(resource, attr, relatedId); + SetNavigation(resource, attr, relatedId, relatedResourceType); // depending on if this base parser is used client-side or server-side, // different additional processing per field needs to be executed. @@ -187,7 +192,8 @@ private void SetHasOneRelationship(IIdentifiable resource, /// Sets the dependent side of a HasOne relationship, which means that a /// foreign key also will to be populated. /// - private void SetForeignKey(IIdentifiable resource, PropertyInfo foreignKey, HasOneAttribute attr, string id) + private void SetForeignKey(IIdentifiable resource, PropertyInfo foreignKey, HasOneAttribute attr, string id, + Type relationshipType) { bool foreignKeyPropertyIsNullableType = Nullable.GetUnderlyingType(foreignKey.PropertyType) != null || foreignKey.PropertyType == typeof(string); @@ -198,7 +204,7 @@ private void SetForeignKey(IIdentifiable resource, PropertyInfo foreignKey, HasO throw new FormatException($"Cannot set required relationship identifier '{attr.IdentifiablePropertyName}' to null because it is a non-nullable type."); } - var typedId = TypeHelper.ConvertStringIdToTypedId(attr.Property.PropertyType, id, ResourceFactory); + var typedId = TypeHelper.ConvertStringIdToTypedId(relationshipType, id, ResourceFactory); foreignKey.SetValue(resource, typedId); } @@ -206,7 +212,8 @@ private void SetForeignKey(IIdentifiable resource, PropertyInfo foreignKey, HasO /// Sets the principal side of a HasOne relationship, which means no /// foreign key is involved. /// - private void SetNavigation(IIdentifiable resource, HasOneAttribute attr, string relatedId) + private void SetNavigation(IIdentifiable resource, HasOneAttribute attr, string relatedId, + Type relationshipType) { if (relatedId == null) { @@ -214,7 +221,7 @@ private void SetNavigation(IIdentifiable resource, HasOneAttribute attr, string } else { - var relatedInstance = (IIdentifiable)ResourceFactory.CreateInstance(attr.RightType); + var relatedInstance = (IIdentifiable)ResourceFactory.CreateInstance(relationshipType); relatedInstance.StringId = relatedId; attr.SetValue(resource, relatedInstance, ResourceFactory); } @@ -232,8 +239,10 @@ private void SetHasManyRelationship( { // if the relationship is set to null, no need to set the navigation property to null: this is the default value. var relatedResources = relationshipData.ManyData.Select(rio => { - var relatedInstance = (IIdentifiable)ResourceFactory.CreateInstance(attr.RightType); + var relatedResourceType = ResourceContextProvider.GetResourceContext(rio.Type).ResourceType; + var relatedInstance = (IIdentifiable)ResourceFactory.CreateInstance(relatedResourceType); relatedInstance.StringId = rio.Id; + return relatedInstance; }); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/SoftDeletion/Company.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/SoftDeletion/Company.cs index 585c965b3d..fe042dec21 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/SoftDeletion/Company.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/SoftDeletion/Company.cs @@ -4,15 +4,14 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.SoftDeletion { - public sealed class Company : Identifiable + public class Article : Identifiable { - [Attr] - public string Name { get; set; } + [Attr(PublicName = "article-prop")] + public string ArticleProp { get; set; } - [Attr] - public bool IsSoftDeleted { get; set; } + public int AuthorId { get; set; } - [HasMany] - public ICollection Departments { get; set; } + [HasOne(PublicName = "author")] + public Person Author{ get; set; } } } From 9104cb93df7428a0136aac6dcf8f68e347106880 Mon Sep 17 00:00:00 2001 From: maurei Date: Fri, 18 Sep 2020 14:11:55 +0200 Subject: [PATCH 02/20] test: fix and add new tests --- .../Acceptance/KebabCaseFormatterTests.cs | 18 +- .../LinksWithoutNamespaceTests.cs | 19 ++- .../ResourceInheritance/Article.cs | 20 +++ .../ResourceInheritance/ArticlesController.cs | 14 ++ .../ResourceInheritance/Person.cs | 16 ++ .../ResourceInheritanceDbContext.cs | 35 ++++ .../ResourceInheritanceTests.cs | 154 ++++++++++++++++++ .../ResourceInheritance/Student.cs | 10 ++ .../ResourceInheritance/StudentsController.cs | 16 ++ .../ResourceInheritance/Teacher.cs | 10 ++ .../ResourceInheritance/TeachersController.cs | 16 ++ .../IntegrationTests/SoftDeletion/Company.cs | 13 +- 12 files changed, 323 insertions(+), 18 deletions(-) create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Article.cs create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ArticlesController.cs create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Person.cs create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceDbContext.cs create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceTests.cs create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Student.cs create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/StudentsController.cs create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Teacher.cs create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/TeachersController.cs diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/KebabCaseFormatterTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/KebabCaseFormatterTests.cs index 49748e4f54..480230360a 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/KebabCaseFormatterTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/KebabCaseFormatterTests.cs @@ -5,9 +5,11 @@ using Bogus; using FluentAssertions; using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Serialization.Client.Internal; using JsonApiDotNetCore.Serialization.Objects; +using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample; using JsonApiDotNetCoreExample.Data; using JsonApiDotNetCoreExample.Models; @@ -15,6 +17,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Serialization; using Xunit; @@ -32,12 +35,6 @@ public KebabCaseFormatterTests(IntegrationTestContext() .RuleFor(m => m.CompoundAttr, f => f.Lorem.Sentence()); - - testContext.ConfigureServicesAfterStartup(services => - { - var part = new AssemblyPart(typeof(EmptyStartup).Assembly); - services.AddMvcCore().ConfigureApplicationPartManager(apm => apm.ApplicationParts.Add(part)); - }); } [Fact] @@ -192,4 +189,13 @@ protected override void ConfigureJsonApiOptions(JsonApiOptions options) ((DefaultContractResolver)options.SerializerSettings.ContractResolver).NamingStrategy = new KebabCaseNamingStrategy(); } } + public sealed class KebabCasedModelsController : JsonApiController + { + public KebabCasedModelsController( + IJsonApiOptions options, + ILoggerFactory loggerFactory, + IResourceService resourceService) + : base(options, loggerFactory, resourceService) + { } + } } diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/LinksWithoutNamespaceTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/LinksWithoutNamespaceTests.cs index 9670d5003a..cf87e6ee85 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/LinksWithoutNamespaceTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/LinksWithoutNamespaceTests.cs @@ -2,13 +2,16 @@ using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Serialization.Objects; +using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample; using JsonApiDotNetCoreExample.Data; using JsonApiDotNetCoreExample.Models; using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Xunit; namespace JsonApiDotNetCoreExampleTests.Acceptance.Spec.DocumentTests @@ -20,12 +23,6 @@ public sealed class LinksWithoutNamespaceTests : IClassFixture testContext) { _testContext = testContext; - - testContext.ConfigureServicesAfterStartup(services => - { - var part = new AssemblyPart(typeof(EmptyStartup).Assembly); - services.AddMvcCore().ConfigureApplicationPartManager(apm => apm.ApplicationParts.Add(part)); - }); } [Fact] @@ -96,4 +93,14 @@ protected override void ConfigureJsonApiOptions(JsonApiOptions options) options.Namespace = null; } } + + public sealed class PeopleController : JsonApiController + { + public PeopleController( + IJsonApiOptions options, + ILoggerFactory loggerFactory, + IResourceService resourceService) + : base(options, loggerFactory, resourceService) + { } + } } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Article.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Article.cs new file mode 100644 index 0000000000..09593a2a85 --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Article.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using JsonApiDotNetCore.Resources; +using JsonApiDotNetCore.Resources.Annotations; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance +{ + public sealed class Article : Identifiable + { + [Attr] + public string Title { get; set; } + + public int? AuthorId { get; set; } + + [HasOne] + public Person Author { get; set; } + + [HasMany] + public List Reviewers { get; set; } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ArticlesController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ArticlesController.cs new file mode 100644 index 0000000000..99501e820d --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ArticlesController.cs @@ -0,0 +1,14 @@ +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Services; +using Microsoft.Extensions.Logging; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance +{ + public sealed class ArticlesController : JsonApiController
+ { + public ArticlesController(IJsonApiOptions options, ILoggerFactory loggerFactory, + IResourceService
resourceService) + : base(options, loggerFactory, resourceService) { } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Person.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Person.cs new file mode 100644 index 0000000000..0e468b6bb2 --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Person.cs @@ -0,0 +1,16 @@ +using JsonApiDotNetCore.Resources; +using JsonApiDotNetCore.Resources.Annotations; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance +{ + public abstract class Person : Identifiable + { + [Attr] + public string InheritedProperty { get; set; } + + [HasOne] + public Article ReviewItem { get; set; } + + public int? ReviewItemId { get; set; } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceDbContext.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceDbContext.cs new file mode 100644 index 0000000000..b4f345b3de --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceDbContext.cs @@ -0,0 +1,35 @@ +using Microsoft.EntityFrameworkCore; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance +{ + public sealed class ResourceInheritanceDbContext : DbContext + { + public ResourceInheritanceDbContext(DbContextOptions options) + : base(options) + { + } + + public DbSet
Articles { get; set; } + public DbSet Persons { get; set; } + public DbSet Students { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .ToTable("Persons") + .HasDiscriminator("Type") + .HasValue(1) + .HasValue(2); + + modelBuilder.Entity
() + .HasOne(t => t.Author) + .WithMany() + .HasForeignKey(a => a.AuthorId); + + modelBuilder.Entity
() + .HasMany(t => t.Reviewers) + .WithOne(p => p.ReviewItem) + .HasForeignKey(p => p.ReviewItemId); + } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceTests.cs new file mode 100644 index 0000000000..3ee123c22a --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceTests.cs @@ -0,0 +1,154 @@ +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using FluentAssertions; +using JsonApiDotNetCore.Resources; +using JsonApiDotNetCore.Serialization.Objects; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance +{ + public sealed class ResourceInheritanceTests : IClassFixture, ResourceInheritanceDbContext>> + { + private readonly IntegrationTestContext, ResourceInheritanceDbContext> _testContext; + + public ResourceInheritanceTests(IntegrationTestContext, ResourceInheritanceDbContext> testContext) + { + _testContext = testContext; + + testContext.ConfigureServicesAfterStartup(services => + { + + }); + } + + [Theory] + [InlineData("students", 0)] + [InlineData("teachers", 1)] + public async Task Can_create_article_with_relationship_that_has_inheritance(string type, int index) + { + // Arrange + var persons = new List + { + new Student() + { + InheritedProperty = "Student", + StudentProperty = "This is a student" + }, + new Teacher() + { + InheritedProperty = "Teacher", + TeacherProperty = "This is a teacher" + } + }; + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + await dbContext.ClearTableAsync
(); + await dbContext.ClearTableAsync(); + await dbContext.Persons.AddRangeAsync(persons); + + await dbContext.SaveChangesAsync(); + }); + + var route = "/articles?include=author"; + var requestBody = new + { + data = new + { + type = "articles", + attributes = new Dictionary + { + { "title", "JsonApiDotNetCore" } + }, + relationships = new Dictionary + { + { "author", new + { + data = new { type, id = persons[index].StringId } + } + } + } + } + }; + + // Act + var (httpResponse, responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); + + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.Created); + + responseDocument.SingleData.Should().NotBeNull(); + responseDocument.SingleData.Relationships["author"].Should().NotBeNull(); + responseDocument.SingleData.Relationships["author"].SingleData.Type.Should().Be(type); + responseDocument.SingleData.Relationships["author"].SingleData.Id.Should().Be(persons[index].StringId); + } + + [Fact] + public async Task Blaat() + { + // Arrange + var persons = new List + { + new Student() + { + InheritedProperty = "Student", + StudentProperty = "This is a student" + }, + new Teacher() + { + InheritedProperty = "Teacher", + TeacherProperty = "This is a teacher" + } + }; + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + await dbContext.ClearTableAsync
(); + await dbContext.ClearTableAsync(); + await dbContext.Persons.AddRangeAsync(persons); + + await dbContext.SaveChangesAsync(); + }); + + var route = "/articles?include=reviewers"; + var requestBody = new + { + data = new + { + type = "articles", + attributes = new Dictionary + { + { "title", "JsonApiDotNetCore" } + }, + relationships = new Dictionary + { + { "reviewers", new + { data = new [] + { + new { type = "students", id = persons[0].StringId }, + new { type = "teachers", id = persons[1].StringId } + } + } + } + } + } + }; + + // Act + var (httpResponse, responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); + + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.Created); + + responseDocument.SingleData.Should().NotBeNull(); + responseDocument.SingleData.Relationships["reviewers"].ManyData.Should().HaveCount(2); + responseDocument.SingleData.Relationships["reviewers"].ManyData[0].Type.Should().Be("students"); + responseDocument.SingleData.Relationships["reviewers"].ManyData[0].Id.Should().Be(persons[0].StringId); + responseDocument.SingleData.Relationships["reviewers"].ManyData[1].Type.Should().Be("teachers"); + responseDocument.SingleData.Relationships["reviewers"].ManyData[1].Id.Should().Be(persons[1].StringId); + } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Student.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Student.cs new file mode 100644 index 0000000000..76463a3477 --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Student.cs @@ -0,0 +1,10 @@ +using JsonApiDotNetCore.Resources.Annotations; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance +{ + public class Student : Person + { + [Attr] + public string StudentProperty { get; set; } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/StudentsController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/StudentsController.cs new file mode 100644 index 0000000000..c94a5e907c --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/StudentsController.cs @@ -0,0 +1,16 @@ +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Services; +using Microsoft.Extensions.Logging; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance +{ + public sealed class StudentsController : JsonApiController + { + public StudentsController(IJsonApiOptions options, ILoggerFactory loggerFactory, + IResourceService resourceService) + : base(options, loggerFactory, resourceService) + { + } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Teacher.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Teacher.cs new file mode 100644 index 0000000000..3f09770e01 --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Teacher.cs @@ -0,0 +1,10 @@ +using JsonApiDotNetCore.Resources.Annotations; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance +{ + public class Teacher : Person + { + [Attr] + public string TeacherProperty { get; set; } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/TeachersController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/TeachersController.cs new file mode 100644 index 0000000000..540042ce49 --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/TeachersController.cs @@ -0,0 +1,16 @@ +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Services; +using Microsoft.Extensions.Logging; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance +{ + public sealed class TeachersController : JsonApiController + { + public TeachersController(IJsonApiOptions options, ILoggerFactory loggerFactory, + IResourceService resourceService) + : base(options, loggerFactory, resourceService) + { + } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/SoftDeletion/Company.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/SoftDeletion/Company.cs index fe042dec21..585c965b3d 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/SoftDeletion/Company.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/SoftDeletion/Company.cs @@ -4,14 +4,15 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.SoftDeletion { - public class Article : Identifiable + public sealed class Company : Identifiable { - [Attr(PublicName = "article-prop")] - public string ArticleProp { get; set; } + [Attr] + public string Name { get; set; } - public int AuthorId { get; set; } + [Attr] + public bool IsSoftDeleted { get; set; } - [HasOne(PublicName = "author")] - public Person Author{ get; set; } + [HasMany] + public ICollection Departments { get; set; } } } From a49a99e123593bf48060a2db5ff65d30b92fc294 Mon Sep 17 00:00:00 2001 From: maurei Date: Fri, 18 Sep 2020 17:39:11 +0200 Subject: [PATCH 03/20] feat: finished resource inheritance --- .../Serialization/JsonApiReader.cs | 2 +- .../Acceptance/KebabCaseFormatterTests.cs | 18 +- .../LinksWithoutNamespaceTests.cs | 19 +- .../ResourceInheritance/Article.cs | 20 -- .../ResourceInheritance/ArticlesController.cs | 14 - .../ResourceInheritance/Controllers.cs | 28 ++ .../ResourceInheritance/Models.cs | 45 +++ .../ResourceInheritance/Person.cs | 16 -- .../ResourceInheritanceDbContext.cs | 32 +-- .../ResourceInheritanceTests.cs | 260 ++++++++++++++---- .../ResourceInheritance/Student.cs | 10 - .../ResourceInheritance/StudentsController.cs | 16 -- .../ResourceInheritance/Teacher.cs | 10 - .../ResourceInheritance/TeachersController.cs | 16 -- 14 files changed, 304 insertions(+), 202 deletions(-) delete mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Article.cs delete mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ArticlesController.cs create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers.cs create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models.cs delete mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Person.cs delete mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Student.cs delete mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/StudentsController.cs delete mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Teacher.cs delete mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/TeachersController.cs diff --git a/src/JsonApiDotNetCore/Serialization/JsonApiReader.cs b/src/JsonApiDotNetCore/Serialization/JsonApiReader.cs index 3ec623964d..d84e2ff526 100644 --- a/src/JsonApiDotNetCore/Serialization/JsonApiReader.cs +++ b/src/JsonApiDotNetCore/Serialization/JsonApiReader.cs @@ -89,7 +89,7 @@ private void ValidateIncomingResourceType(InputFormatterContext context, object var bodyResourceTypes = GetBodyResourceTypes(model); foreach (var bodyResourceType in bodyResourceTypes) { - if (bodyResourceType != endpointResourceType) + if (!endpointResourceType.IsAssignableFrom(bodyResourceType)) { var resourceFromEndpoint = _resourceContextProvider.GetResourceContext(endpointResourceType); var resourceFromBody = _resourceContextProvider.GetResourceContext(bodyResourceType); diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/KebabCaseFormatterTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/KebabCaseFormatterTests.cs index 480230360a..49748e4f54 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/KebabCaseFormatterTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/KebabCaseFormatterTests.cs @@ -5,11 +5,9 @@ using Bogus; using FluentAssertions; using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Serialization.Client.Internal; using JsonApiDotNetCore.Serialization.Objects; -using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample; using JsonApiDotNetCoreExample.Data; using JsonApiDotNetCoreExample.Models; @@ -17,7 +15,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Serialization; using Xunit; @@ -35,6 +32,12 @@ public KebabCaseFormatterTests(IntegrationTestContext() .RuleFor(m => m.CompoundAttr, f => f.Lorem.Sentence()); + + testContext.ConfigureServicesAfterStartup(services => + { + var part = new AssemblyPart(typeof(EmptyStartup).Assembly); + services.AddMvcCore().ConfigureApplicationPartManager(apm => apm.ApplicationParts.Add(part)); + }); } [Fact] @@ -189,13 +192,4 @@ protected override void ConfigureJsonApiOptions(JsonApiOptions options) ((DefaultContractResolver)options.SerializerSettings.ContractResolver).NamingStrategy = new KebabCaseNamingStrategy(); } } - public sealed class KebabCasedModelsController : JsonApiController - { - public KebabCasedModelsController( - IJsonApiOptions options, - ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { } - } } diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/LinksWithoutNamespaceTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/LinksWithoutNamespaceTests.cs index cf87e6ee85..9670d5003a 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/LinksWithoutNamespaceTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/LinksWithoutNamespaceTests.cs @@ -2,16 +2,13 @@ using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Serialization.Objects; -using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample; using JsonApiDotNetCoreExample.Data; using JsonApiDotNetCoreExample.Models; using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; using Xunit; namespace JsonApiDotNetCoreExampleTests.Acceptance.Spec.DocumentTests @@ -23,6 +20,12 @@ public sealed class LinksWithoutNamespaceTests : IClassFixture testContext) { _testContext = testContext; + + testContext.ConfigureServicesAfterStartup(services => + { + var part = new AssemblyPart(typeof(EmptyStartup).Assembly); + services.AddMvcCore().ConfigureApplicationPartManager(apm => apm.ApplicationParts.Add(part)); + }); } [Fact] @@ -93,14 +96,4 @@ protected override void ConfigureJsonApiOptions(JsonApiOptions options) options.Namespace = null; } } - - public sealed class PeopleController : JsonApiController - { - public PeopleController( - IJsonApiOptions options, - ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { } - } } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Article.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Article.cs deleted file mode 100644 index 09593a2a85..0000000000 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Article.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Collections.Generic; -using JsonApiDotNetCore.Resources; -using JsonApiDotNetCore.Resources.Annotations; - -namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance -{ - public sealed class Article : Identifiable - { - [Attr] - public string Title { get; set; } - - public int? AuthorId { get; set; } - - [HasOne] - public Person Author { get; set; } - - [HasMany] - public List Reviewers { get; set; } - } -} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ArticlesController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ArticlesController.cs deleted file mode 100644 index 99501e820d..0000000000 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ArticlesController.cs +++ /dev/null @@ -1,14 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.Logging; - -namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance -{ - public sealed class ArticlesController : JsonApiController
- { - public ArticlesController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService
resourceService) - : base(options, loggerFactory, resourceService) { } - } -} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers.cs new file mode 100644 index 0000000000..51e05651a3 --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers.cs @@ -0,0 +1,28 @@ +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Services; +using Microsoft.Extensions.Logging; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance +{ + public sealed class MalesController : JsonApiController + { + public MalesController(IJsonApiOptions options, ILoggerFactory loggerFactory, + IResourceService resourceService) + : base(options, loggerFactory, resourceService) { } + } + + public sealed class FemalesController : JsonApiController + { + public FemalesController(IJsonApiOptions options, ILoggerFactory loggerFactory, + IResourceService resourceService) + : base(options, loggerFactory, resourceService) { } + } + + public sealed class PlaceholdersController : JsonApiController + { + public PlaceholdersController(IJsonApiOptions options, ILoggerFactory loggerFactory, + IResourceService resourceService) + : base(options, loggerFactory, resourceService) { } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models.cs new file mode 100644 index 0000000000..d25dd80a6e --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; +using JsonApiDotNetCore.Resources; +using JsonApiDotNetCore.Resources.Annotations; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance +{ + public abstract class Person : Identifiable { } + + public sealed class Male : Person + { + [Attr] + public string MaleProperty { get; set; } + } + + public sealed class Female : Person + { + [Attr] + public string FemaleProperty { get; set; } + } + + public sealed class Placeholder : Identifiable + { + [HasOne] + public Person OneToOnePerson { get; set; } + public int? OneToOnePersonId { get; set; } + + [HasMany] + public List OneToManyPersons { get; set; } + + [NotMapped] + [HasManyThrough(nameof(PlaceholderPersons))] + public List ManyToManyPersons { get; set; } + public List PlaceholderPersons { get; set; } + } + + public sealed class PlaceholderPerson + { + public int PlaceHolderId { get; set; } + public Placeholder PlaceHolder { get; set; } + + public int PersonId { get; set; } + public Person Person { get; set; } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Person.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Person.cs deleted file mode 100644 index 0e468b6bb2..0000000000 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Person.cs +++ /dev/null @@ -1,16 +0,0 @@ -using JsonApiDotNetCore.Resources; -using JsonApiDotNetCore.Resources.Annotations; - -namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance -{ - public abstract class Person : Identifiable - { - [Attr] - public string InheritedProperty { get; set; } - - [HasOne] - public Article ReviewItem { get; set; } - - public int? ReviewItemId { get; set; } - } -} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceDbContext.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceDbContext.cs index b4f345b3de..e6a528ce9a 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceDbContext.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceDbContext.cs @@ -5,31 +5,27 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance public sealed class ResourceInheritanceDbContext : DbContext { public ResourceInheritanceDbContext(DbContextOptions options) - : base(options) - { - } - - public DbSet
Articles { get; set; } + : base(options) { } + + public DbSet Placeholders { get; set; } public DbSet Persons { get; set; } - public DbSet Students { get; set; } + + public DbSet Males { get; set; } + + public DbSet Females { get; set; } + + public DbSet RelatedBaseResources { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity() .ToTable("Persons") .HasDiscriminator("Type") - .HasValue(1) - .HasValue(2); - - modelBuilder.Entity
() - .HasOne(t => t.Author) - .WithMany() - .HasForeignKey(a => a.AuthorId); - - modelBuilder.Entity
() - .HasMany(t => t.Reviewers) - .WithOne(p => p.ReviewItem) - .HasForeignKey(p => p.ReviewItemId); + .HasValue(1) + .HasValue(2); + + modelBuilder.Entity() + .HasKey(pp => new { pp.PlaceHolderId, pp.PersonId }); } } } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceTests.cs index 3ee123c22a..bfb46737c2 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceTests.cs @@ -5,6 +5,7 @@ using FluentAssertions; using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Serialization.Objects; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Xunit; @@ -13,61 +14,132 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance public sealed class ResourceInheritanceTests : IClassFixture, ResourceInheritanceDbContext>> { private readonly IntegrationTestContext, ResourceInheritanceDbContext> _testContext; + private readonly List _persons = new List { new Male(), new Female() }; + private readonly Placeholder _placeholder = new Placeholder(); public ResourceInheritanceTests(IntegrationTestContext, ResourceInheritanceDbContext> testContext) { _testContext = testContext; + } - testContext.ConfigureServicesAfterStartup(services => + [Theory] + [InlineData("males", 0)] + [InlineData("females", 1)] + public async Task Can_create_resource_with_one_to_one_relationship_that_has_inheritance(string type, int index) + { + // Arrange + await _testContext.RunOnDatabaseAsync(async dbContext => { + await dbContext.ClearTableAsync(); + await dbContext.ClearTableAsync(); + await dbContext.ClearTableAsync(); + await dbContext.Persons.AddRangeAsync(_persons); + await dbContext.SaveChangesAsync(); }); + + var route = "/placeholders?include=oneToOnePerson"; + var requestBody = new + { + data = new + { + type = "placeholders", + relationships = new Dictionary + { + { "oneToOnePerson", new + { + data = new { type, id = _persons[index].StringId } + } + } + } + } + }; + + // Act + var (httpResponse, responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); + + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.Created); + + responseDocument.SingleData.Should().NotBeNull(); + responseDocument.SingleData.Relationships["oneToOnePerson"].Should().NotBeNull(); + responseDocument.SingleData.Relationships["oneToOnePerson"].SingleData.Type.Should().Be(type); + responseDocument.SingleData.Relationships["oneToOnePerson"].SingleData.Id.Should().Be(_persons[index].StringId); } [Theory] - [InlineData("students", 0)] - [InlineData("teachers", 1)] - public async Task Can_create_article_with_relationship_that_has_inheritance(string type, int index) + [InlineData("males", 0)] + [InlineData("females", 1)] + public async Task Can_patch_one_to_one_relationship_that_has_inheritance_through_relationship_endpoint(string type, int index) { // Arrange - var persons = new List + var expectedType = _persons[index].GetType(); + + await _testContext.RunOnDatabaseAsync(async dbContext => { - new Student() - { - InheritedProperty = "Student", - StudentProperty = "This is a student" - }, - new Teacher() - { - InheritedProperty = "Teacher", - TeacherProperty = "This is a teacher" - } + await dbContext.ClearTableAsync(); + await dbContext.ClearTableAsync(); + await dbContext.ClearTableAsync(); + await dbContext.Persons.AddRangeAsync(_persons); + await dbContext.Placeholders.AddAsync(_placeholder); + + await dbContext.SaveChangesAsync(); + }); + + + var route = $"/placeholders/{_placeholder.Id}/relationships/oneToOnePerson"; + + var requestBody = new + { + data = new { type, id = _persons[index].StringId } }; + // Act + var (httpResponse, _) = await _testContext.ExecutePatchAsync(route, requestBody); + + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + await dbContext.SaveChangesAsync(); + var assertPlaceholder = await dbContext.Placeholders + .Include(p => p.OneToOnePerson) + .Where(p => p.Id.Equals(_placeholder.Id)).FirstAsync(); + + assertPlaceholder.OneToOnePerson.GetType().Should().Be(expectedType); + }); + } + + + [Fact] + public async Task Can_create_resource_with_one_to_many_relationship_that_has_inheritance() + { + // Arrange await _testContext.RunOnDatabaseAsync(async dbContext => { - await dbContext.ClearTableAsync
(); + await dbContext.ClearTableAsync(); await dbContext.ClearTableAsync(); - await dbContext.Persons.AddRangeAsync(persons); + await dbContext.ClearTableAsync(); + await dbContext.Persons.AddRangeAsync(_persons); await dbContext.SaveChangesAsync(); }); - var route = "/articles?include=author"; + var route = "/placeholders?include=oneToManyPersons"; var requestBody = new { data = new { - type = "articles", - attributes = new Dictionary - { - { "title", "JsonApiDotNetCore" } - }, + type = "placeholders", relationships = new Dictionary { - { "author", new - { - data = new { type, id = persons[index].StringId } + { "oneToManyPersons", new + { data = new [] + { + new { type = "males", id = _persons[0].StringId }, + new { type = "females", id = _persons[1].StringId } + } } } } @@ -81,55 +153,85 @@ await _testContext.RunOnDatabaseAsync(async dbContext => httpResponse.Should().HaveStatusCode(HttpStatusCode.Created); responseDocument.SingleData.Should().NotBeNull(); - responseDocument.SingleData.Relationships["author"].Should().NotBeNull(); - responseDocument.SingleData.Relationships["author"].SingleData.Type.Should().Be(type); - responseDocument.SingleData.Relationships["author"].SingleData.Id.Should().Be(persons[index].StringId); + responseDocument.SingleData.Relationships["oneToManyPersons"].ManyData.Should().HaveCount(2); + responseDocument.SingleData.Relationships["oneToManyPersons"].ManyData[0].Type.Should().Be("males"); + responseDocument.SingleData.Relationships["oneToManyPersons"].ManyData[0].Id.Should().Be(_persons[0].StringId); + responseDocument.SingleData.Relationships["oneToManyPersons"].ManyData[1].Type.Should().Be("females"); + responseDocument.SingleData.Relationships["oneToManyPersons"].ManyData[1].Id.Should().Be(_persons[1].StringId); } [Fact] - public async Task Blaat() + public async Task Can_patch_one_to_many_relationship_that_has_inheritance_through_relationship_endpoint() { - // Arrange - var persons = new List + // Arrange + + await _testContext.RunOnDatabaseAsync(async dbContext => { - new Student() - { - InheritedProperty = "Student", - StudentProperty = "This is a student" - }, - new Teacher() + await dbContext.ClearTableAsync(); + await dbContext.ClearTableAsync(); + await dbContext.ClearTableAsync(); + await dbContext.Persons.AddRangeAsync(_persons); + await dbContext.Placeholders.AddAsync(_placeholder); + + await dbContext.SaveChangesAsync(); + }); + + var route = $"/placeholders/{_placeholder.Id}/relationships/oneToManyPersons"; + var requestBody = new + { + data = new [] { - InheritedProperty = "Teacher", - TeacherProperty = "This is a teacher" + new { type = "males", id = _persons[0].StringId }, + new { type = "females", id = _persons[1].StringId } } }; + // Act + var (httpResponse, _) = await _testContext.ExecutePatchAsync(route, requestBody); + + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); + await _testContext.RunOnDatabaseAsync(async dbContext => { - await dbContext.ClearTableAsync
(); + await dbContext.SaveChangesAsync(); + var assertPlaceholder = await dbContext.Placeholders + .Include(p => p.OneToManyPersons) + .Where(p => p.Id.Equals(_placeholder.Id)).FirstAsync(); + + assertPlaceholder.OneToManyPersons.Should().HaveCount(2); + assertPlaceholder.OneToManyPersons.Should().ContainSingle(p => p is Male); + assertPlaceholder.OneToManyPersons.Should().ContainSingle(p => p is Female); + }); + } + + [Fact] + public async Task Can_create_resource_with_many_to_many_relationship_that_has_inheritance() + { + // Arrange + await _testContext.RunOnDatabaseAsync(async dbContext => + { + await dbContext.ClearTableAsync(); await dbContext.ClearTableAsync(); - await dbContext.Persons.AddRangeAsync(persons); + await dbContext.ClearTableAsync(); + await dbContext.Persons.AddRangeAsync(_persons); await dbContext.SaveChangesAsync(); }); - var route = "/articles?include=reviewers"; + var route = "/placeholders?include=manyToManyPersons"; var requestBody = new { data = new { - type = "articles", - attributes = new Dictionary - { - { "title", "JsonApiDotNetCore" } - }, + type = "placeholders", relationships = new Dictionary { - { "reviewers", new + { "manyToManyPersons", new { data = new [] { - new { type = "students", id = persons[0].StringId }, - new { type = "teachers", id = persons[1].StringId } + new { type = "males", id = _persons[0].StringId }, + new { type = "females", id = _persons[1].StringId } } } } @@ -144,11 +246,57 @@ await _testContext.RunOnDatabaseAsync(async dbContext => httpResponse.Should().HaveStatusCode(HttpStatusCode.Created); responseDocument.SingleData.Should().NotBeNull(); - responseDocument.SingleData.Relationships["reviewers"].ManyData.Should().HaveCount(2); - responseDocument.SingleData.Relationships["reviewers"].ManyData[0].Type.Should().Be("students"); - responseDocument.SingleData.Relationships["reviewers"].ManyData[0].Id.Should().Be(persons[0].StringId); - responseDocument.SingleData.Relationships["reviewers"].ManyData[1].Type.Should().Be("teachers"); - responseDocument.SingleData.Relationships["reviewers"].ManyData[1].Id.Should().Be(persons[1].StringId); + responseDocument.SingleData.Relationships["manyToManyPersons"].ManyData.Should().HaveCount(2); + responseDocument.SingleData.Relationships["manyToManyPersons"].ManyData[0].Type.Should().Be("males"); + responseDocument.SingleData.Relationships["manyToManyPersons"].ManyData[0].Id.Should().Be(_persons[0].StringId); + responseDocument.SingleData.Relationships["manyToManyPersons"].ManyData[1].Type.Should().Be("females"); + responseDocument.SingleData.Relationships["manyToManyPersons"].ManyData[1].Id.Should().Be(_persons[1].StringId); + } + + [Fact] + public async Task Can_patch_many_to_many_relationship_that_has_inheritance_through_relationship_endpoint() + { + // Arrange + await _testContext.RunOnDatabaseAsync(async dbContext => + { + await dbContext.ClearTableAsync(); + await dbContext.ClearTableAsync(); + await dbContext.ClearTableAsync(); + await dbContext.Persons.AddRangeAsync(_persons); + await dbContext.Placeholders.AddAsync(_placeholder); + + await dbContext.SaveChangesAsync(); + }); + + var route = $"/placeholders/{_placeholder.Id}/relationships/manyToManyPersons"; + var requestBody = new + { + data = new [] + { + new { type = "males", id = _persons[0].StringId }, + new { type = "females", id = _persons[1].StringId } + } + }; + + // Act + var (httpResponse, _) = await _testContext.ExecutePatchAsync(route, requestBody); + + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + await dbContext.SaveChangesAsync(); + var manyToManyPersons = (await dbContext.Placeholders + .Include(p => p.PlaceholderPersons) + .ThenInclude(pp => pp.Person) + .Where(p => p.Id.Equals(_placeholder.Id)).FirstAsync()) + .PlaceholderPersons.Select(pp => pp.Person).ToList(); + + manyToManyPersons.Should().HaveCount(2); + manyToManyPersons.Should().ContainSingle(p => p is Male); + manyToManyPersons.Should().ContainSingle(p => p is Female); + }); } } } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Student.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Student.cs deleted file mode 100644 index 76463a3477..0000000000 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Student.cs +++ /dev/null @@ -1,10 +0,0 @@ -using JsonApiDotNetCore.Resources.Annotations; - -namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance -{ - public class Student : Person - { - [Attr] - public string StudentProperty { get; set; } - } -} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/StudentsController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/StudentsController.cs deleted file mode 100644 index c94a5e907c..0000000000 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/StudentsController.cs +++ /dev/null @@ -1,16 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.Logging; - -namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance -{ - public sealed class StudentsController : JsonApiController - { - public StudentsController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { - } - } -} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Teacher.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Teacher.cs deleted file mode 100644 index 3f09770e01..0000000000 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Teacher.cs +++ /dev/null @@ -1,10 +0,0 @@ -using JsonApiDotNetCore.Resources.Annotations; - -namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance -{ - public class Teacher : Person - { - [Attr] - public string TeacherProperty { get; set; } - } -} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/TeachersController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/TeachersController.cs deleted file mode 100644 index 540042ce49..0000000000 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/TeachersController.cs +++ /dev/null @@ -1,16 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.Logging; - -namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance -{ - public sealed class TeachersController : JsonApiController - { - public TeachersController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { - } - } -} From b8837da22ea2f2fdc016fe926b6db6e04806a52a Mon Sep 17 00:00:00 2001 From: maurei Date: Fri, 18 Sep 2020 17:41:44 +0200 Subject: [PATCH 04/20] fix: whitespace --- .../IntegrationTests/ResourceInheritance/Models.cs | 4 ++++ .../ResourceInheritance/ResourceInheritanceDbContext.cs | 1 + 2 files changed, 5 insertions(+) diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models.cs index d25dd80a6e..388b64a73a 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models.cs @@ -23,6 +23,7 @@ public sealed class Placeholder : Identifiable { [HasOne] public Person OneToOnePerson { get; set; } + public int? OneToOnePersonId { get; set; } [HasMany] @@ -31,15 +32,18 @@ public sealed class Placeholder : Identifiable [NotMapped] [HasManyThrough(nameof(PlaceholderPersons))] public List ManyToManyPersons { get; set; } + public List PlaceholderPersons { get; set; } } public sealed class PlaceholderPerson { public int PlaceHolderId { get; set; } + public Placeholder PlaceHolder { get; set; } public int PersonId { get; set; } + public Person Person { get; set; } } } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceDbContext.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceDbContext.cs index e6a528ce9a..766a63ccb5 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceDbContext.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceDbContext.cs @@ -8,6 +8,7 @@ public ResourceInheritanceDbContext(DbContextOptions Placeholders { get; set; } + public DbSet Persons { get; set; } public DbSet Males { get; set; } From 8a7392c51757193d4c467c402e342aee9fca0666 Mon Sep 17 00:00:00 2001 From: maurei Date: Fri, 18 Sep 2020 18:54:50 +0200 Subject: [PATCH 05/20] fix: tests --- .../Controllers/KebabCasedModelsController.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/Examples/JsonApiDotNetCoreExample/Controllers/KebabCasedModelsController.cs diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/KebabCasedModelsController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/KebabCasedModelsController.cs new file mode 100644 index 0000000000..05f48389c1 --- /dev/null +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/KebabCasedModelsController.cs @@ -0,0 +1,18 @@ +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Services; +using JsonApiDotNetCoreExample.Models; +using Microsoft.Extensions.Logging; + +namespace JsonApiDotNetCoreExample.Controllers +{ + public sealed class KebabCasedModelsController : JsonApiController + { + public KebabCasedModelsController( + IJsonApiOptions options, + ILoggerFactory loggerFactory, + IResourceService resourceService) + : base(options, loggerFactory, resourceService) + { } + } +} From 1a8f41bf2c57e8ada46203a9e8e1b40c75552ccf Mon Sep 17 00:00:00 2001 From: maurei Date: Thu, 24 Sep 2020 12:02:48 +0200 Subject: [PATCH 06/20] chore: review --- .../Controllers/KebabCasedModelsController.cs | 16 +++++----- .../Serialization/BaseDeserializer.cs | 2 +- .../ResourceInheritance/Controllers.cs | 28 ------------------ .../Controllers/FemalesController.cs | 14 +++++++++ .../Controllers/MalesController.cs | 14 +++++++++ .../Controllers/PlaceholdersController.cs | 14 +++++++++ .../ResourceInheritance/Models/Female.cs | 10 +++++++ .../ResourceInheritance/Models/Male.cs | 10 +++++++ .../ResourceInheritance/Models/Person.cs | 6 ++++ .../{Models.cs => Models/Placeholder.cs} | 29 +------------------ .../Models/PlaceholderPerson.cs | 13 +++++++++ .../ResourceInheritanceTests.cs | 3 -- 12 files changed, 91 insertions(+), 68 deletions(-) delete mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers.cs create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/FemalesController.cs create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/MalesController.cs create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/PlaceholdersController.cs create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Female.cs create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Male.cs create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Person.cs rename test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/{Models.cs => Models/Placeholder.cs} (51%) create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/PlaceholderPerson.cs diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/KebabCasedModelsController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/KebabCasedModelsController.cs index 05f48389c1..a473241247 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/KebabCasedModelsController.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Controllers/KebabCasedModelsController.cs @@ -6,13 +6,13 @@ namespace JsonApiDotNetCoreExample.Controllers { - public sealed class KebabCasedModelsController : JsonApiController - { - public KebabCasedModelsController( - IJsonApiOptions options, - ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { } + public sealed class KebabCasedModelsController : JsonApiController + { + public KebabCasedModelsController( + IJsonApiOptions options, + ILoggerFactory loggerFactory, + IResourceService resourceService) + : base(options, loggerFactory, resourceService) + { } } } diff --git a/src/JsonApiDotNetCore/Serialization/BaseDeserializer.cs b/src/JsonApiDotNetCore/Serialization/BaseDeserializer.cs index c7cecc7226..f2795692d1 100644 --- a/src/JsonApiDotNetCore/Serialization/BaseDeserializer.cs +++ b/src/JsonApiDotNetCore/Serialization/BaseDeserializer.cs @@ -190,7 +190,7 @@ private void SetHasOneRelationship(IIdentifiable resource, /// /// Sets the dependent side of a HasOne relationship, which means that a - /// foreign key also will to be populated. + /// foreign key also will be populated. /// private void SetForeignKey(IIdentifiable resource, PropertyInfo foreignKey, HasOneAttribute attr, string id, Type relationshipType) diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers.cs deleted file mode 100644 index 51e05651a3..0000000000 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers.cs +++ /dev/null @@ -1,28 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.Logging; - -namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance -{ - public sealed class MalesController : JsonApiController - { - public MalesController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, loggerFactory, resourceService) { } - } - - public sealed class FemalesController : JsonApiController - { - public FemalesController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, loggerFactory, resourceService) { } - } - - public sealed class PlaceholdersController : JsonApiController - { - public PlaceholdersController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, loggerFactory, resourceService) { } - } -} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/FemalesController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/FemalesController.cs new file mode 100644 index 0000000000..9826de5a25 --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/FemalesController.cs @@ -0,0 +1,14 @@ +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Services; +using Microsoft.Extensions.Logging; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance +{ + public sealed class FemalesController : JsonApiController + { + public FemalesController(IJsonApiOptions options, ILoggerFactory loggerFactory, + IResourceService resourceService) + : base(options, loggerFactory, resourceService) { } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/MalesController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/MalesController.cs new file mode 100644 index 0000000000..3d0caecc68 --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/MalesController.cs @@ -0,0 +1,14 @@ +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Services; +using Microsoft.Extensions.Logging; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance +{ + public sealed class MalesController : JsonApiController + { + public MalesController(IJsonApiOptions options, ILoggerFactory loggerFactory, + IResourceService resourceService) + : base(options, loggerFactory, resourceService) { } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/PlaceholdersController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/PlaceholdersController.cs new file mode 100644 index 0000000000..9385a31f13 --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/PlaceholdersController.cs @@ -0,0 +1,14 @@ +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Services; +using Microsoft.Extensions.Logging; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance +{ + public sealed class PlaceholdersController : JsonApiController + { + public PlaceholdersController(IJsonApiOptions options, ILoggerFactory loggerFactory, + IResourceService resourceService) + : base(options, loggerFactory, resourceService) { } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Female.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Female.cs new file mode 100644 index 0000000000..d80bd9f466 --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Female.cs @@ -0,0 +1,10 @@ +using JsonApiDotNetCore.Resources.Annotations; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance +{ + public sealed class Female : Person + { + [Attr] + public string FemaleProperty { get; set; } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Male.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Male.cs new file mode 100644 index 0000000000..198840426f --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Male.cs @@ -0,0 +1,10 @@ +using JsonApiDotNetCore.Resources.Annotations; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance +{ + public sealed class Male : Person + { + [Attr] + public string MaleProperty { get; set; } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Person.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Person.cs new file mode 100644 index 0000000000..f7c293e6e9 --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Person.cs @@ -0,0 +1,6 @@ +using JsonApiDotNetCore.Resources; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance +{ + public abstract class Person : Identifiable { } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Placeholder.cs similarity index 51% rename from test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models.cs rename to test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Placeholder.cs index 388b64a73a..16de1f8043 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Placeholder.cs @@ -5,27 +5,11 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance { - public abstract class Person : Identifiable { } - - public sealed class Male : Person - { - [Attr] - public string MaleProperty { get; set; } - } - - public sealed class Female : Person - { - [Attr] - public string FemaleProperty { get; set; } - } - public sealed class Placeholder : Identifiable { [HasOne] public Person OneToOnePerson { get; set; } - - public int? OneToOnePersonId { get; set; } - + [HasMany] public List OneToManyPersons { get; set; } @@ -35,15 +19,4 @@ public sealed class Placeholder : Identifiable public List PlaceholderPersons { get; set; } } - - public sealed class PlaceholderPerson - { - public int PlaceHolderId { get; set; } - - public Placeholder PlaceHolder { get; set; } - - public int PersonId { get; set; } - - public Person Person { get; set; } - } } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/PlaceholderPerson.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/PlaceholderPerson.cs new file mode 100644 index 0000000000..c8a6fc3041 --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/PlaceholderPerson.cs @@ -0,0 +1,13 @@ +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance +{ + public sealed class PlaceholderPerson + { + public int PlaceHolderId { get; set; } + + public Placeholder PlaceHolder { get; set; } + + public int PersonId { get; set; } + + public Person Person { get; set; } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceTests.cs index bfb46737c2..5b0b4bac04 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceTests.cs @@ -3,10 +3,8 @@ using System.Net; using System.Threading.Tasks; using FluentAssertions; -using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Serialization.Objects; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; using Xunit; namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance @@ -164,7 +162,6 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_patch_one_to_many_relationship_that_has_inheritance_through_relationship_endpoint() { // Arrange - await _testContext.RunOnDatabaseAsync(async dbContext => { await dbContext.ClearTableAsync(); From 172126e7e4cdda475fa8af127b0bfb791411709d Mon Sep 17 00:00:00 2001 From: maurei Date: Thu, 24 Sep 2020 19:30:28 +0200 Subject: [PATCH 07/20] feat: improved inheritance support. Partially doneE --- .../Configuration/ResourceGraphBuilder.cs | 2 +- .../EntityFrameworkCoreRepository.cs | 6 +- .../Serialization/BaseDeserializer.cs | 10 +- .../Controllers/CatsController.cs | 16 + .../Controllers/DogsController.cs | 16 + .../Controllers/FictionBooksController.cs | 16 + .../Controllers/NonFictionBooksController.cs | 16 + ...ldersController.cs => PeopleController.cs} | 6 +- .../ResourceInheritance/Models/Animal.cs | 6 + .../ResourceInheritance/Models/Cat.cs | 4 + .../ResourceInheritance/Models/Dog.cs | 4 + .../ResourceInheritance/Models/Female.cs | 6 +- .../ResourceInheritance/Models/FictionBook.cs | 4 + .../ResourceInheritance/Models/Literature.cs | 6 + ...aceholderPerson.cs => LiteraturePerson.cs} | 6 +- .../ResourceInheritance/Models/Male.cs | 6 +- .../Models/NonFictionBook.cs | 4 + .../ResourceInheritance/Models/Person.cs | 22 +- .../ResourceInheritance/Models/Placeholder.cs | 22 - .../ResourceInheritanceDbContext.cs | 28 +- .../ResourceInheritanceTests.cs | 405 +++++++++++------- 21 files changed, 413 insertions(+), 198 deletions(-) create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/CatsController.cs create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/DogsController.cs create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/FictionBooksController.cs create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/NonFictionBooksController.cs rename test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/{PlaceholdersController.cs => PeopleController.cs} (56%) create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Animal.cs create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Cat.cs create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Dog.cs create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/FictionBook.cs create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Literature.cs rename test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/{PlaceholderPerson.cs => LiteraturePerson.cs} (58%) create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/NonFictionBook.cs delete mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Placeholder.cs diff --git a/src/JsonApiDotNetCore/Configuration/ResourceGraphBuilder.cs b/src/JsonApiDotNetCore/Configuration/ResourceGraphBuilder.cs index cd08d5a222..51375f9f90 100644 --- a/src/JsonApiDotNetCore/Configuration/ResourceGraphBuilder.cs +++ b/src/JsonApiDotNetCore/Configuration/ResourceGraphBuilder.cs @@ -188,7 +188,7 @@ private IReadOnlyCollection GetRelationships(Type resourc var throughProperties = throughType.GetProperties(); // ArticleTag.Article - hasManyThroughAttribute.LeftProperty = throughProperties.SingleOrDefault(x => x.PropertyType == resourceType) + hasManyThroughAttribute.LeftProperty = throughProperties.SingleOrDefault(x => x.PropertyType.IsAssignableFrom(resourceType)) ?? throw new InvalidConfigurationException($"{throughType} does not contain a navigation property to type {resourceType}"); // ArticleTag.ArticleId diff --git a/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs b/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs index 6803f8a8ea..2131ab8e6c 100644 --- a/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs +++ b/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs @@ -269,9 +269,11 @@ private IEnumerable GetTrackedManyRelationshipValue(IEnumerable r { var tracked = AttachOrGetTracked(pointer); if (tracked != null) newWasAlreadyAttached = true; + + var trackedPointer = tracked ?? pointer; - // we should recalculate the target type for every iteration because types may vary. This is possible with resource inheritance - return Convert.ChangeType(tracked ?? pointer, (tracked ?? pointer).GetType()); + // We should recalculate the target type for every iteration because types may vary. This is possible with resource inheritance. + return Convert.ChangeType(trackedPointer, trackedPointer.GetType()); }), relationshipAttr.Property.PropertyType); if (newWasAlreadyAttached) wasAlreadyAttached = true; diff --git a/src/JsonApiDotNetCore/Serialization/BaseDeserializer.cs b/src/JsonApiDotNetCore/Serialization/BaseDeserializer.cs index f2795692d1..5e2e94a757 100644 --- a/src/JsonApiDotNetCore/Serialization/BaseDeserializer.cs +++ b/src/JsonApiDotNetCore/Serialization/BaseDeserializer.cs @@ -169,7 +169,7 @@ private void SetHasOneRelationship(IIdentifiable resource, var rio = (ResourceIdentifierObject)relationshipData.Data; var relatedId = rio?.Id; - var relatedResourceType = relationshipData.SingleData == null + var relationshipType = relationshipData.SingleData == null ? attr.RightType : ResourceContextProvider.GetResourceContext(relationshipData.SingleData.Type).ResourceType; @@ -179,9 +179,9 @@ private void SetHasOneRelationship(IIdentifiable resource, if (foreignKeyProperty != null) // there is a FK from the current resource pointing to the related object, // i.e. we're populating the relationship from the dependent side. - SetForeignKey(resource, foreignKeyProperty, attr, relatedId, relatedResourceType); + SetForeignKey(resource, foreignKeyProperty, attr, relatedId, relationshipType); - SetNavigation(resource, attr, relatedId, relatedResourceType); + SetNavigation(resource, attr, relatedId, relationshipType); // depending on if this base parser is used client-side or server-side, // different additional processing per field needs to be executed. @@ -239,8 +239,8 @@ private void SetHasManyRelationship( { // if the relationship is set to null, no need to set the navigation property to null: this is the default value. var relatedResources = relationshipData.ManyData.Select(rio => { - var relatedResourceType = ResourceContextProvider.GetResourceContext(rio.Type).ResourceType; - var relatedInstance = (IIdentifiable)ResourceFactory.CreateInstance(relatedResourceType); + var relationshipType = ResourceContextProvider.GetResourceContext(rio.Type).ResourceType; + var relatedInstance = (IIdentifiable)ResourceFactory.CreateInstance(relationshipType); relatedInstance.StringId = rio.Id; return relatedInstance; diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/CatsController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/CatsController.cs new file mode 100644 index 0000000000..746e7b6248 --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/CatsController.cs @@ -0,0 +1,16 @@ +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Services; +using Microsoft.Extensions.Logging; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance +{ + public sealed class CatsController : JsonApiController + { + public CatsController(IJsonApiOptions options, ILoggerFactory loggerFactory, + IResourceService resourceService) + : base(options, loggerFactory, resourceService) + { + } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/DogsController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/DogsController.cs new file mode 100644 index 0000000000..917620bb41 --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/DogsController.cs @@ -0,0 +1,16 @@ +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Services; +using Microsoft.Extensions.Logging; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance +{ + public sealed class DogsController : JsonApiController + { + public DogsController(IJsonApiOptions options, ILoggerFactory loggerFactory, + IResourceService resourceService) + : base(options, loggerFactory, resourceService) + { + } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/FictionBooksController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/FictionBooksController.cs new file mode 100644 index 0000000000..f4a65a2a7d --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/FictionBooksController.cs @@ -0,0 +1,16 @@ +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Services; +using Microsoft.Extensions.Logging; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance +{ + public sealed class FictionBooksController : JsonApiController + { + public FictionBooksController(IJsonApiOptions options, ILoggerFactory loggerFactory, + IResourceService resourceService) + : base(options, loggerFactory, resourceService) + { + } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/NonFictionBooksController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/NonFictionBooksController.cs new file mode 100644 index 0000000000..ae6259d704 --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/NonFictionBooksController.cs @@ -0,0 +1,16 @@ +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Services; +using Microsoft.Extensions.Logging; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance +{ + public sealed class NonFictionBooksController : JsonApiController + { + public NonFictionBooksController(IJsonApiOptions options, ILoggerFactory loggerFactory, + IResourceService resourceService) + : base(options, loggerFactory, resourceService) + { + } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/PlaceholdersController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/PeopleController.cs similarity index 56% rename from test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/PlaceholdersController.cs rename to test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/PeopleController.cs index 9385a31f13..d1a635f7e5 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/PlaceholdersController.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/PeopleController.cs @@ -5,10 +5,10 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance { - public sealed class PlaceholdersController : JsonApiController + public sealed class PeopleController : JsonApiController { - public PlaceholdersController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) + public PeopleController(IJsonApiOptions options, ILoggerFactory loggerFactory, + IResourceService resourceService) : base(options, loggerFactory, resourceService) { } } } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Animal.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Animal.cs new file mode 100644 index 0000000000..dbdc974818 --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Animal.cs @@ -0,0 +1,6 @@ +using JsonApiDotNetCore.Resources; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance +{ + public abstract class Animal : Identifiable { } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Cat.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Cat.cs new file mode 100644 index 0000000000..f9f520ae12 --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Cat.cs @@ -0,0 +1,4 @@ +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance +{ + public class Cat : Animal { } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Dog.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Dog.cs new file mode 100644 index 0000000000..06de85b801 --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Dog.cs @@ -0,0 +1,4 @@ +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance +{ + public class Dog : Animal { } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Female.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Female.cs index d80bd9f466..87062b4d6c 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Female.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Female.cs @@ -2,9 +2,5 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance { - public sealed class Female : Person - { - [Attr] - public string FemaleProperty { get; set; } - } + public sealed class Female : Person { } } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/FictionBook.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/FictionBook.cs new file mode 100644 index 0000000000..3f2d30c889 --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/FictionBook.cs @@ -0,0 +1,4 @@ +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance +{ + public class FictionBook : Literature { } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Literature.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Literature.cs new file mode 100644 index 0000000000..d3a3d674cb --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Literature.cs @@ -0,0 +1,6 @@ +using JsonApiDotNetCore.Resources; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance +{ + public abstract class Literature : Identifiable { } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/PlaceholderPerson.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/LiteraturePerson.cs similarity index 58% rename from test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/PlaceholderPerson.cs rename to test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/LiteraturePerson.cs index c8a6fc3041..da1b87ec0e 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/PlaceholderPerson.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/LiteraturePerson.cs @@ -1,10 +1,10 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance { - public sealed class PlaceholderPerson + public sealed class LiteraturePerson { - public int PlaceHolderId { get; set; } + public int LiteratureId { get; set; } - public Placeholder PlaceHolder { get; set; } + public Literature Literature { get; set; } public int PersonId { get; set; } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Male.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Male.cs index 198840426f..ca6067b7d0 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Male.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Male.cs @@ -2,9 +2,5 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance { - public sealed class Male : Person - { - [Attr] - public string MaleProperty { get; set; } - } + public sealed class Male : Person { } } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/NonFictionBook.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/NonFictionBook.cs new file mode 100644 index 0000000000..52fa0f28fe --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/NonFictionBook.cs @@ -0,0 +1,4 @@ +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance +{ + public class NonFictionBook : Literature { } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Person.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Person.cs index f7c293e6e9..ef98a27774 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Person.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Person.cs @@ -1,6 +1,26 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; using JsonApiDotNetCore.Resources; +using JsonApiDotNetCore.Resources.Annotations; namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance { - public abstract class Person : Identifiable { } + public abstract class Person : Identifiable + { + [HasOne] + public Animal Pet { get; set; } + + [HasMany] + public List Parents { get; set; } + + [NotMapped] + [HasManyThrough(nameof(PersonLiterature))] + public List FavoriteLiterature { get; set; } + + public List PersonLiterature { get; set; } + } } + + + + diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Placeholder.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Placeholder.cs deleted file mode 100644 index 16de1f8043..0000000000 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Placeholder.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations.Schema; -using JsonApiDotNetCore.Resources; -using JsonApiDotNetCore.Resources.Annotations; - -namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance -{ - public sealed class Placeholder : Identifiable - { - [HasOne] - public Person OneToOnePerson { get; set; } - - [HasMany] - public List OneToManyPersons { get; set; } - - [NotMapped] - [HasManyThrough(nameof(PlaceholderPersons))] - public List ManyToManyPersons { get; set; } - - public List PlaceholderPersons { get; set; } - } -} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceDbContext.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceDbContext.cs index 766a63ccb5..dbd2b7e0a7 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceDbContext.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceDbContext.cs @@ -7,15 +7,19 @@ public sealed class ResourceInheritanceDbContext : DbContext public ResourceInheritanceDbContext(DbContextOptions options) : base(options) { } - public DbSet Placeholders { get; set; } + public DbSet People { get; set; } - public DbSet Persons { get; set; } + public DbSet Cats { get; set; } - public DbSet Males { get; set; } + public DbSet Dogs { get; set; } public DbSet Females { get; set; } - public DbSet RelatedBaseResources { get; set; } + public DbSet Males { get; set; } + + public DbSet FictionBooks { get; set; } + + public DbSet NonFictionBooks { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -25,8 +29,20 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasValue(1) .HasValue(2); - modelBuilder.Entity() - .HasKey(pp => new { pp.PlaceHolderId, pp.PersonId }); + modelBuilder.Entity() + .ToTable("Animals") + .HasDiscriminator("Type") + .HasValue(1) + .HasValue(2); + + modelBuilder.Entity() + .ToTable("Books") + .HasDiscriminator("Type") + .HasValue(1) + .HasValue(2); + + modelBuilder.Entity() + .HasKey(pp => new { pp.LiteratureId, pp.PersonId }); } } } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceTests.cs index 5b0b4bac04..f76d706d90 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceTests.cs @@ -9,45 +9,157 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance { - public sealed class ResourceInheritanceTests : IClassFixture, ResourceInheritanceDbContext>> + public sealed class ResourceInheritanceTests : IClassFixture, ResourceInheritanceDbContext>> { - private readonly IntegrationTestContext, ResourceInheritanceDbContext> _testContext; - private readonly List _persons = new List { new Male(), new Female() }; - private readonly Placeholder _placeholder = new Placeholder(); + private readonly + IntegrationTestContext, ResourceInheritanceDbContext> + _testContext; - public ResourceInheritanceTests(IntegrationTestContext, ResourceInheritanceDbContext> testContext) + public ResourceInheritanceTests( + IntegrationTestContext, ResourceInheritanceDbContext> + testContext) { _testContext = testContext; + + _testContext.RunOnDatabaseAsync(async dbContext => + { + await dbContext.ClearTableAsync(); + await dbContext.ClearTableAsync(); + await dbContext.ClearTableAsync(); + await dbContext.ClearTableAsync(); + await dbContext.ClearTableAsync(); + await dbContext.ClearTableAsync(); + await dbContext.ClearTableAsync(); + + await dbContext.SaveChangesAsync(); + }).Wait(); + } + + [Fact] + public async Task When_including_to_one_relationship_should_be_successful() + { + // Arrange + var person = new Male() + { + Pet = new Cat(), + }; + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + await dbContext.AddAsync(person); + await dbContext.SaveChangesAsync(); + }); + + var route = "/people?include=pet"; + + // Act + var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.Created); + + responseDocument.ManyData.Should().HaveCount(1); + responseDocument.SingleData.Relationships["pet"].Should().NotBeNull(); + responseDocument.SingleData.Relationships["pet"].SingleData.Type.Should().Be("cats"); + responseDocument.SingleData.Relationships["pet"].SingleData.Id.Should().Be(person.Pet.StringId); } + + [Fact] + public async Task When_including_to_many_relationship_should_be_successful() + { + // Arrange + var person = new Male() + { + Parents = new List { new Male(), new Female() }, + }; + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + await dbContext.AddAsync(person); + await dbContext.SaveChangesAsync(); + }); + + var route = "/people?include=parents"; + + // Act + var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); - [Theory] - [InlineData("males", 0)] - [InlineData("females", 1)] - public async Task Can_create_resource_with_one_to_one_relationship_that_has_inheritance(string type, int index) + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.Created); + + responseDocument.ManyData.Should().HaveCount(3); + responseDocument.ManyData[0].Relationships["parents"].Should().NotBeNull(); + responseDocument.ManyData[1].Relationships["parents"].Should().BeNull(); + responseDocument.ManyData[2].Relationships["parents"].Should().BeNull(); + responseDocument.ManyData[0].Relationships["parents"].ManyData.Should().HaveCount(2); + responseDocument.ManyData[0].Relationships["parents"].ManyData[0].Id.Should().Be(person.Parents[0].StringId); + responseDocument.ManyData[0].Relationships["parents"].ManyData[0].Type.Should().Be("males"); + responseDocument.ManyData[0].Relationships["parents"].ManyData[1].Id.Should().Be(person.Parents[1].StringId); + responseDocument.ManyData[0].Relationships["parents"].ManyData[1].Type.Should().Be("females"); + } + + [Fact] + public async Task When_including_many_to_many_relationship_should_be_successful() { // Arrange + var person = new Male() + { + PersonLiterature = new List + { + new LiteraturePerson { Literature = new FictionBook() }, + new LiteraturePerson { Literature = new NonFictionBook() } + } + }; + await _testContext.RunOnDatabaseAsync(async dbContext => { - await dbContext.ClearTableAsync(); - await dbContext.ClearTableAsync(); - await dbContext.ClearTableAsync(); - await dbContext.Persons.AddRangeAsync(_persons); + await dbContext.AddAsync(person); + await dbContext.SaveChangesAsync(); + }); + + var route = "/people?include=favoriteLiterature"; + + // Act + var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.Created); + responseDocument.ManyData.Should().HaveCount(1); + responseDocument.ManyData[0].Relationships["favoriteLiterature"].Should().NotBeNull(); + responseDocument.ManyData[0].Relationships["favoriteLiterature"].ManyData.Should().HaveCount(2); + responseDocument.ManyData[0].Relationships["favoriteLiterature"].ManyData[0].Id.Should().Be(person.PersonLiterature[0].Literature.StringId); + responseDocument.ManyData[0].Relationships["favoriteLiterature"].ManyData[0].Type.Should().Be("fictionBooks"); + responseDocument.ManyData[0].Relationships["favoriteLiterature"].ManyData[1].Id.Should().Be(person.PersonLiterature[1].Literature.StringId); + responseDocument.ManyData[0].Relationships["favoriteLiterature"].ManyData[1].Type.Should().Be("nonFictionBooks"); + } + + [Fact] + public async Task When_creating_resource_with_to_one_relationship_should_be_successful() + { + // Arrange + var cat = new Cat(); + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + await dbContext.AddAsync(cat); await dbContext.SaveChangesAsync(); }); - var route = "/placeholders?include=oneToOnePerson"; + var route = "/people"; var requestBody = new { data = new { - type = "placeholders", + type = "males", relationships = new Dictionary { - { "oneToOnePerson", new + { + "pet", new { - data = new { type, id = _persons[index].StringId } - } + data = new {type = "cats", id = cat.StringId} + } } } } @@ -55,90 +167,88 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Act var (httpResponse, responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); - + // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.Created); - - responseDocument.SingleData.Should().NotBeNull(); - responseDocument.SingleData.Relationships["oneToOnePerson"].Should().NotBeNull(); - responseDocument.SingleData.Relationships["oneToOnePerson"].SingleData.Type.Should().Be(type); - responseDocument.SingleData.Relationships["oneToOnePerson"].SingleData.Id.Should().Be(_persons[index].StringId); + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + var assertPerson = await dbContext.People + .Include(p => p.Pet) + .Where(p => p.Id.Equals(int.Parse(responseDocument.SingleData.Id))).FirstAsync(); + + assertPerson.Pet.GetType().Should().Be(cat.GetType()); + }); } - [Theory] - [InlineData("males", 0)] - [InlineData("females", 1)] - public async Task Can_patch_one_to_one_relationship_that_has_inheritance_through_relationship_endpoint(string type, int index) + + [Fact] + public async Task When_patching_resource_with_to_one_relationship_through_relationship_link_should_be_successful() { // Arrange - var expectedType = _persons[index].GetType(); + var person = new Male(); + var cat = new Cat(); await _testContext.RunOnDatabaseAsync(async dbContext => { - await dbContext.ClearTableAsync(); - await dbContext.ClearTableAsync(); - await dbContext.ClearTableAsync(); - await dbContext.Persons.AddRangeAsync(_persons); - await dbContext.Placeholders.AddAsync(_placeholder); - + await dbContext.AddRangeAsync(person, cat); await dbContext.SaveChangesAsync(); }); - - - var route = $"/placeholders/{_placeholder.Id}/relationships/oneToOnePerson"; + + var route = $"/people/{person.Id}/relationships/pet"; var requestBody = new { - data = new { type, id = _persons[index].StringId } + data = new {type = "cats", id = cat.StringId} }; // Act - var (httpResponse, _) = await _testContext.ExecutePatchAsync(route, requestBody); - + var (httpResponse, responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); + // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); - + await _testContext.RunOnDatabaseAsync(async dbContext => { - await dbContext.SaveChangesAsync(); - var assertPlaceholder = await dbContext.Placeholders - .Include(p => p.OneToOnePerson) - .Where(p => p.Id.Equals(_placeholder.Id)).FirstAsync(); + var assertPerson = await dbContext.People + .Include(p => p.Pet) + .Where(p => p.Id.Equals(person.Id)).FirstAsync(); - assertPlaceholder.OneToOnePerson.GetType().Should().Be(expectedType); + assertPerson.Pet.GetType().Should().Be(cat.GetType()); }); } - + [Fact] - public async Task Can_create_resource_with_one_to_many_relationship_that_has_inheritance() + public async Task When_creating_resource_with_to_many_relationship_should_be_successful() { // Arrange + var father = new Male(); + var mother = new Female(); + await _testContext.RunOnDatabaseAsync(async dbContext => { - await dbContext.ClearTableAsync(); - await dbContext.ClearTableAsync(); - await dbContext.ClearTableAsync(); - await dbContext.Persons.AddRangeAsync(_persons); - + await dbContext.AddRangeAsync(father, mother); await dbContext.SaveChangesAsync(); }); - var route = "/placeholders?include=oneToManyPersons"; + var route = "/people"; var requestBody = new { data = new { - type = "placeholders", + type = "males", relationships = new Dictionary { - { "oneToManyPersons", new - { data = new [] + { + "parents", new + { + data = new[] { - new { type = "males", id = _persons[0].StringId }, - new { type = "females", id = _persons[1].StringId } + new { type = "males", id = father.StringId }, + new { type = "females", id = mother.StringId } } - } + } } } } @@ -146,153 +256,158 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Act var (httpResponse, responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); - + // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.Created); - - responseDocument.SingleData.Should().NotBeNull(); - responseDocument.SingleData.Relationships["oneToManyPersons"].ManyData.Should().HaveCount(2); - responseDocument.SingleData.Relationships["oneToManyPersons"].ManyData[0].Type.Should().Be("males"); - responseDocument.SingleData.Relationships["oneToManyPersons"].ManyData[0].Id.Should().Be(_persons[0].StringId); - responseDocument.SingleData.Relationships["oneToManyPersons"].ManyData[1].Type.Should().Be("females"); - responseDocument.SingleData.Relationships["oneToManyPersons"].ManyData[1].Id.Should().Be(_persons[1].StringId); + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + var assertPerson = await dbContext.People + .Include(p => p.Parents) + .Where(p => p.Id.Equals(int.Parse(responseDocument.SingleData.Id))).FirstAsync(); + + assertPerson.Parents.Should().HaveCount(2); + assertPerson.Parents.Should().ContainSingle(p => p is Male); + assertPerson.Parents.Should().ContainSingle(p => p is Female); + }); } - + [Fact] - public async Task Can_patch_one_to_many_relationship_that_has_inheritance_through_relationship_endpoint() + public async Task When_patching_resource_with_to_many_relationship_through_relationship_link_should_be_successful() { - // Arrange + // Arrange + var child = new Male(); + var father = new Male(); + var mother = new Female(); + await _testContext.RunOnDatabaseAsync(async dbContext => { - await dbContext.ClearTableAsync(); - await dbContext.ClearTableAsync(); - await dbContext.ClearTableAsync(); - await dbContext.Persons.AddRangeAsync(_persons); - await dbContext.Placeholders.AddAsync(_placeholder); - + await dbContext.AddRangeAsync(child, father, mother); await dbContext.SaveChangesAsync(); }); - - var route = $"/placeholders/{_placeholder.Id}/relationships/oneToManyPersons"; + + var route = $"/people/{child.StringId}/relationships/parents"; var requestBody = new { - data = new [] + data = new[] { - new { type = "males", id = _persons[0].StringId }, - new { type = "females", id = _persons[1].StringId } + new { type = "males", id = father.StringId }, + new { type = "females", id = mother.StringId } } }; - + // Act var (httpResponse, _) = await _testContext.ExecutePatchAsync(route, requestBody); - + // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); - + await _testContext.RunOnDatabaseAsync(async dbContext => { - await dbContext.SaveChangesAsync(); - var assertPlaceholder = await dbContext.Placeholders - .Include(p => p.OneToManyPersons) - .Where(p => p.Id.Equals(_placeholder.Id)).FirstAsync(); - - assertPlaceholder.OneToManyPersons.Should().HaveCount(2); - assertPlaceholder.OneToManyPersons.Should().ContainSingle(p => p is Male); - assertPlaceholder.OneToManyPersons.Should().ContainSingle(p => p is Female); + var assertChild = await dbContext.Males + .Include(p => p.Parents) + .Where(p => p.Id.Equals(child.Id)).FirstAsync(); + + assertChild.Parents.Should().HaveCount(2); + assertChild.Parents.Should().ContainSingle(p => p is Male); + assertChild.Parents.Should().ContainSingle(p => p is Female); }); } - + [Fact] - public async Task Can_create_resource_with_many_to_many_relationship_that_has_inheritance() + public async Task When_creating_resource_with_many_to_many_relationship_should_be_successful() { // Arrange + var fiction = new FictionBook(); + var nonFiction = new NonFictionBook(); + await _testContext.RunOnDatabaseAsync(async dbContext => { - await dbContext.ClearTableAsync(); - await dbContext.ClearTableAsync(); - await dbContext.ClearTableAsync(); - await dbContext.Persons.AddRangeAsync(_persons); - + await dbContext.AddRangeAsync(fiction, nonFiction); await dbContext.SaveChangesAsync(); }); - - var route = "/placeholders?include=manyToManyPersons"; + + var route = "/males?include=favoriteLiterature"; var requestBody = new { data = new { - type = "placeholders", + type = "males", relationships = new Dictionary { - { "manyToManyPersons", new - { data = new [] + { + "favoriteLiterature", new + { + data = new[] { - new { type = "males", id = _persons[0].StringId }, - new { type = "females", id = _persons[1].StringId } + new { type = "fictionBooks", id = fiction.StringId }, + new { type = "nonFictionBooks", id = nonFiction.StringId } } - } + } } } } }; - + // Act var (httpResponse, responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); - + // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.Created); - - responseDocument.SingleData.Should().NotBeNull(); - responseDocument.SingleData.Relationships["manyToManyPersons"].ManyData.Should().HaveCount(2); - responseDocument.SingleData.Relationships["manyToManyPersons"].ManyData[0].Type.Should().Be("males"); - responseDocument.SingleData.Relationships["manyToManyPersons"].ManyData[0].Id.Should().Be(_persons[0].StringId); - responseDocument.SingleData.Relationships["manyToManyPersons"].ManyData[1].Type.Should().Be("females"); - responseDocument.SingleData.Relationships["manyToManyPersons"].ManyData[1].Id.Should().Be(_persons[1].StringId); + await _testContext.RunOnDatabaseAsync(async dbContext => + { + var favoriteLiterature = (await dbContext.People + .Include(p => p.PersonLiterature) + .ThenInclude(pp => pp.Literature) + .Where(p => p.Id.Equals(int.Parse(responseDocument.SingleData.Id))).FirstAsync()) + .PersonLiterature.Select(pp => pp.Literature).ToList(); + + favoriteLiterature.Should().HaveCount(2); + favoriteLiterature.Should().ContainSingle(p => p is FictionBook); + favoriteLiterature.Should().ContainSingle(p => p is NonFictionBook); + }); } - + [Fact] - public async Task Can_patch_many_to_many_relationship_that_has_inheritance_through_relationship_endpoint() + public async Task When_patching_resource_with_many_to_many_relationship_through_relationship_link_should_be_successful() { // Arrange + var fiction = new FictionBook(); + var nonFiction = new NonFictionBook(); + var person = new Male(); + await _testContext.RunOnDatabaseAsync(async dbContext => { - await dbContext.ClearTableAsync(); - await dbContext.ClearTableAsync(); - await dbContext.ClearTableAsync(); - await dbContext.Persons.AddRangeAsync(_persons); - await dbContext.Placeholders.AddAsync(_placeholder); - + await dbContext.AddRangeAsync(fiction, nonFiction, person); await dbContext.SaveChangesAsync(); }); - - var route = $"/placeholders/{_placeholder.Id}/relationships/manyToManyPersons"; + + var route = $"/people/{person.Id}/relationships/favoriteLiterature"; var requestBody = new { - data = new [] + data = new[] { - new { type = "males", id = _persons[0].StringId }, - new { type = "females", id = _persons[1].StringId } + new { type = "fictionBooks", id = fiction.StringId }, + new { type = "nonFictionBooks", id = nonFiction.StringId } } }; - + // Act var (httpResponse, _) = await _testContext.ExecutePatchAsync(route, requestBody); - + // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); - + await _testContext.RunOnDatabaseAsync(async dbContext => { - await dbContext.SaveChangesAsync(); - var manyToManyPersons = (await dbContext.Placeholders - .Include(p => p.PlaceholderPersons) - .ThenInclude(pp => pp.Person) - .Where(p => p.Id.Equals(_placeholder.Id)).FirstAsync()) - .PlaceholderPersons.Select(pp => pp.Person).ToList(); - - manyToManyPersons.Should().HaveCount(2); - manyToManyPersons.Should().ContainSingle(p => p is Male); - manyToManyPersons.Should().ContainSingle(p => p is Female); + var favoriteLiterature = (await dbContext.Males + .Include(p => p.PersonLiterature) + .ThenInclude(pp => pp.Literature) + .Where(p => p.Id.Equals(person.Id)).FirstAsync()) + .PersonLiterature.Select(pp => pp.Literature).ToList(); + + favoriteLiterature.Should().HaveCount(2); + favoriteLiterature.Should().ContainSingle(p => p is FictionBook); + favoriteLiterature.Should().ContainSingle(p => p is NonFictionBook); }); } } From 0d2047534c379afa0799a2ec8c0086c80e130e78 Mon Sep 17 00:00:00 2001 From: maurei Date: Fri, 25 Sep 2020 10:00:00 +0200 Subject: [PATCH 08/20] fix: tests --- .../QueryableBuilding/QueryableBuilder.cs | 8 +++---- .../Resources/ResourceFactory.cs | 2 +- .../ResourceInheritance/Models/Animal.cs | 7 +++++- .../ResourceInheritance/Models/Cat.cs | 14 +++++++++++- .../ResourceInheritance/Models/Dog.cs | 8 ++++++- .../ResourceInheritanceTests.cs | 22 +++++++++---------- 6 files changed, 42 insertions(+), 19 deletions(-) diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryableBuilder.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryableBuilder.cs index ac64c6c15e..795c310cf5 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryableBuilder.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryableBuilder.cs @@ -64,10 +64,10 @@ public Expression ApplyQuery(QueryLayer layer) expression = ApplyPagination(expression, layer.Pagination); } - if (layer.Projection != null && layer.Projection.Any()) - { - expression = ApplyProjection(expression, layer.Projection, layer.ResourceContext); - } + // if (layer.Projection != null && layer.Projection.Any()) + // { + // expression = ApplyProjection(expression, layer.Projection, layer.ResourceContext); + // } return expression; } diff --git a/src/JsonApiDotNetCore/Resources/ResourceFactory.cs b/src/JsonApiDotNetCore/Resources/ResourceFactory.cs index 5352ea4bd7..1b3d1eb5c8 100644 --- a/src/JsonApiDotNetCore/Resources/ResourceFactory.cs +++ b/src/JsonApiDotNetCore/Resources/ResourceFactory.cs @@ -59,7 +59,7 @@ private static object InnerCreateInstance(Type type, IServiceProvider servicePro public NewExpression CreateNewExpression(Type resourceType) { if (resourceType == null) throw new ArgumentNullException(nameof(resourceType)); - + if (HasSingleConstructorWithoutParameters(resourceType)) { return Expression.New(resourceType); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Animal.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Animal.cs index dbdc974818..5dd6ac0613 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Animal.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Animal.cs @@ -1,6 +1,11 @@ using JsonApiDotNetCore.Resources; +using JsonApiDotNetCore.Resources.Annotations; namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance { - public abstract class Animal : Identifiable { } + public abstract class Animal : Identifiable + { + [Attr] + public bool Feline { get; set; } + } } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Cat.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Cat.cs index f9f520ae12..ad84762f85 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Cat.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Cat.cs @@ -1,4 +1,16 @@ +using System; +using JsonApiDotNetCore.Resources.Annotations; + namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance { - public class Cat : Animal { } + public class Cat : Animal + { + public Cat() + { + Feline = true; + } + + [Attr] + public bool Meows { get; set; } + } } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Dog.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Dog.cs index 06de85b801..f389f59482 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Dog.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Dog.cs @@ -1,4 +1,10 @@ +using JsonApiDotNetCore.Resources.Annotations; + namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance { - public class Dog : Animal { } + public class Dog : Animal + { + [Attr] + public bool Barks { get; set; } + } } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceTests.cs index f76d706d90..3b142bb09d 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceTests.cs @@ -42,7 +42,7 @@ public async Task When_including_to_one_relationship_should_be_successful() // Arrange var person = new Male() { - Pet = new Cat(), + Pet = new Cat() }; await _testContext.RunOnDatabaseAsync(async dbContext => @@ -57,12 +57,12 @@ await _testContext.RunOnDatabaseAsync(async dbContext => var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.Created); + httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); responseDocument.ManyData.Should().HaveCount(1); - responseDocument.SingleData.Relationships["pet"].Should().NotBeNull(); - responseDocument.SingleData.Relationships["pet"].SingleData.Type.Should().Be("cats"); - responseDocument.SingleData.Relationships["pet"].SingleData.Id.Should().Be(person.Pet.StringId); + responseDocument.ManyData[0].Relationships["pet"].HasResource.Should().BeTrue(); + responseDocument.ManyData[0].Relationships["pet"].SingleData.Type.Should().Be("cats"); + responseDocument.ManyData[0].Relationships["pet"].SingleData.Id.Should().Be(person.Pet.StringId); } [Fact] @@ -86,12 +86,12 @@ await _testContext.RunOnDatabaseAsync(async dbContext => var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.Created); + httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); responseDocument.ManyData.Should().HaveCount(3); - responseDocument.ManyData[0].Relationships["parents"].Should().NotBeNull(); - responseDocument.ManyData[1].Relationships["parents"].Should().BeNull(); - responseDocument.ManyData[2].Relationships["parents"].Should().BeNull(); + responseDocument.ManyData[0].Relationships["parents"].HasResource.Should().BeTrue(); + responseDocument.ManyData[1].Relationships["parents"].HasResource.Should().BeFalse(); + responseDocument.ManyData[2].Relationships["parents"].HasResource.Should().BeFalse(); responseDocument.ManyData[0].Relationships["parents"].ManyData.Should().HaveCount(2); responseDocument.ManyData[0].Relationships["parents"].ManyData[0].Id.Should().Be(person.Parents[0].StringId); responseDocument.ManyData[0].Relationships["parents"].ManyData[0].Type.Should().Be("males"); @@ -124,10 +124,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.Created); + httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); responseDocument.ManyData.Should().HaveCount(1); - responseDocument.ManyData[0].Relationships["favoriteLiterature"].Should().NotBeNull(); + responseDocument.ManyData[0].Relationships["favoriteLiterature"].HasResource.Should().BeTrue(); responseDocument.ManyData[0].Relationships["favoriteLiterature"].ManyData.Should().HaveCount(2); responseDocument.ManyData[0].Relationships["favoriteLiterature"].ManyData[0].Id.Should().Be(person.PersonLiterature[0].Literature.StringId); responseDocument.ManyData[0].Relationships["favoriteLiterature"].ManyData[0].Type.Should().Be("fictionBooks"); From b4649b35d2ad5e465116cb7ac0cbbde7284e5774 Mon Sep 17 00:00:00 2001 From: maurei Date: Fri, 25 Sep 2020 10:01:28 +0200 Subject: [PATCH 09/20] chore: revert unnecessary changes --- .../Internal/QueryableBuilding/QueryableBuilder.cs | 8 ++++---- src/JsonApiDotNetCore/Resources/ResourceFactory.cs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryableBuilder.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryableBuilder.cs index 795c310cf5..ac64c6c15e 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryableBuilder.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/QueryableBuilder.cs @@ -64,10 +64,10 @@ public Expression ApplyQuery(QueryLayer layer) expression = ApplyPagination(expression, layer.Pagination); } - // if (layer.Projection != null && layer.Projection.Any()) - // { - // expression = ApplyProjection(expression, layer.Projection, layer.ResourceContext); - // } + if (layer.Projection != null && layer.Projection.Any()) + { + expression = ApplyProjection(expression, layer.Projection, layer.ResourceContext); + } return expression; } diff --git a/src/JsonApiDotNetCore/Resources/ResourceFactory.cs b/src/JsonApiDotNetCore/Resources/ResourceFactory.cs index 1b3d1eb5c8..5352ea4bd7 100644 --- a/src/JsonApiDotNetCore/Resources/ResourceFactory.cs +++ b/src/JsonApiDotNetCore/Resources/ResourceFactory.cs @@ -59,7 +59,7 @@ private static object InnerCreateInstance(Type type, IServiceProvider servicePro public NewExpression CreateNewExpression(Type resourceType) { if (resourceType == null) throw new ArgumentNullException(nameof(resourceType)); - + if (HasSingleConstructorWithoutParameters(resourceType)) { return Expression.New(resourceType); From 198ffcf107d3f8199682583891c486a8b2996760 Mon Sep 17 00:00:00 2001 From: maurei Date: Fri, 25 Sep 2020 10:12:10 +0200 Subject: [PATCH 10/20] test: reveal issue with filtering --- .../Controllers/AnimalsController.cs | 14 ++++++++++ .../ResourceInheritanceTests.cs | 26 ++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/AnimalsController.cs diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/AnimalsController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/AnimalsController.cs new file mode 100644 index 0000000000..10c9f45f1d --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/AnimalsController.cs @@ -0,0 +1,14 @@ +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Services; +using Microsoft.Extensions.Logging; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance +{ + public sealed class AnimalsController : JsonApiController + { + public AnimalsController(IJsonApiOptions options, ILoggerFactory loggerFactory, + IResourceService resourceService) + : base(options, loggerFactory, resourceService) { } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceTests.cs index 3b142bb09d..30247810ae 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceTests.cs @@ -98,7 +98,31 @@ await _testContext.RunOnDatabaseAsync(async dbContext => responseDocument.ManyData[0].Relationships["parents"].ManyData[1].Id.Should().Be(person.Parents[1].StringId); responseDocument.ManyData[0].Relationships["parents"].ManyData[1].Type.Should().Be("females"); } - + + [Fact] + public async Task Can_filter_in_primary_resources() + { + // Arrange + var animals = new List { new Cat { Meows = false }, new Cat { Meows = true }, new Dog() }; + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + await dbContext.AddRangeAsync(animals); + await dbContext.SaveChangesAsync(); + }); + + var route = "/animals?filter=equals(meows,'false')"; + + // Act + var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); + + responseDocument.ManyData.Should().HaveCount(1); + responseDocument.ManyData[0].Id.Should().Be(animals[0].StringId); + } + [Fact] public async Task When_including_many_to_many_relationship_should_be_successful() { From c0d39946f5e6bfea9b57d1184139e8ea778b0375 Mon Sep 17 00:00:00 2001 From: maurei Date: Mon, 28 Sep 2020 12:03:24 +0200 Subject: [PATCH 11/20] fix: better error handling inheritance bug --- .../Controllers/KebabCasedModelsController.cs | 18 ------------------ .../Resources/ResourceFactory.cs | 5 +++++ .../Acceptance/KebabCaseFormatterTests.cs | 19 +++++++++++++------ .../LinksWithoutNamespaceTests.cs | 6 ------ .../ResourceInheritance/Models/Person.cs | 6 ++++++ .../ResourceInheritanceTests.cs | 6 +++--- 6 files changed, 27 insertions(+), 33 deletions(-) delete mode 100644 src/Examples/JsonApiDotNetCoreExample/Controllers/KebabCasedModelsController.cs diff --git a/src/Examples/JsonApiDotNetCoreExample/Controllers/KebabCasedModelsController.cs b/src/Examples/JsonApiDotNetCoreExample/Controllers/KebabCasedModelsController.cs deleted file mode 100644 index a473241247..0000000000 --- a/src/Examples/JsonApiDotNetCoreExample/Controllers/KebabCasedModelsController.cs +++ /dev/null @@ -1,18 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Services; -using JsonApiDotNetCoreExample.Models; -using Microsoft.Extensions.Logging; - -namespace JsonApiDotNetCoreExample.Controllers -{ - public sealed class KebabCasedModelsController : JsonApiController - { - public KebabCasedModelsController( - IJsonApiOptions options, - ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { } - } -} diff --git a/src/JsonApiDotNetCore/Resources/ResourceFactory.cs b/src/JsonApiDotNetCore/Resources/ResourceFactory.cs index 5352ea4bd7..4502939f5b 100644 --- a/src/JsonApiDotNetCore/Resources/ResourceFactory.cs +++ b/src/JsonApiDotNetCore/Resources/ResourceFactory.cs @@ -117,6 +117,11 @@ private static ConstructorInfo GetLongestConstructor(Type type) { ConstructorInfo[] constructors = type.GetConstructors().Where(c => !c.IsStatic).ToArray(); + if (constructors.Length == 0) + { + throw new InvalidOperationException($"No public constructors found for '{type.FullName}'."); + } + ConstructorInfo bestMatch = constructors[0]; int maxParameterLength = constructors[0].GetParameters().Length; diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/KebabCaseFormatterTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/KebabCaseFormatterTests.cs index 49748e4f54..7b3c137a35 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/KebabCaseFormatterTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/KebabCaseFormatterTests.cs @@ -5,9 +5,11 @@ using Bogus; using FluentAssertions; using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Serialization.Client.Internal; using JsonApiDotNetCore.Serialization.Objects; +using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample; using JsonApiDotNetCoreExample.Data; using JsonApiDotNetCoreExample.Models; @@ -15,6 +17,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Serialization; using Xunit; @@ -32,12 +35,6 @@ public KebabCaseFormatterTests(IntegrationTestContext() .RuleFor(m => m.CompoundAttr, f => f.Lorem.Sentence()); - - testContext.ConfigureServicesAfterStartup(services => - { - var part = new AssemblyPart(typeof(EmptyStartup).Assembly); - services.AddMvcCore().ConfigureApplicationPartManager(apm => apm.ApplicationParts.Add(part)); - }); } [Fact] @@ -192,4 +189,14 @@ protected override void ConfigureJsonApiOptions(JsonApiOptions options) ((DefaultContractResolver)options.SerializerSettings.ContractResolver).NamingStrategy = new KebabCaseNamingStrategy(); } } + + public sealed class KebabCasedModelsController : JsonApiController + { + public KebabCasedModelsController( + IJsonApiOptions options, + ILoggerFactory loggerFactory, + IResourceService resourceService) + : base(options, loggerFactory, resourceService) + { } + } } diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/LinksWithoutNamespaceTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/LinksWithoutNamespaceTests.cs index 9670d5003a..6b8d1d970b 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/LinksWithoutNamespaceTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/LinksWithoutNamespaceTests.cs @@ -20,12 +20,6 @@ public sealed class LinksWithoutNamespaceTests : IClassFixture testContext) { _testContext = testContext; - - testContext.ConfigureServicesAfterStartup(services => - { - var part = new AssemblyPart(typeof(EmptyStartup).Assembly); - services.AddMvcCore().ConfigureApplicationPartManager(apm => apm.ApplicationParts.Add(part)); - }); } [Fact] diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Person.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Person.cs index ef98a27774..c36553905b 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Person.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Person.cs @@ -7,8 +7,14 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance { public abstract class Person : Identifiable { + [Attr] + public string Name { get; set; } + [HasOne] public Animal Pet { get; set; } + + [HasOne] + public Cat FavCat { get; set; } [HasMany] public List Parents { get; set; } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceTests.cs index 30247810ae..bac56839fc 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceTests.cs @@ -42,7 +42,7 @@ public async Task When_including_to_one_relationship_should_be_successful() // Arrange var person = new Male() { - Pet = new Cat() + Pet = new Cat { Meows = true } }; await _testContext.RunOnDatabaseAsync(async dbContext => @@ -111,7 +111,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - var route = "/animals?filter=equals(meows,'false')"; + var route = "/animals?filter=equals(feline,'false')"; // Act var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); @@ -120,7 +120,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); responseDocument.ManyData.Should().HaveCount(1); - responseDocument.ManyData[0].Id.Should().Be(animals[0].StringId); + responseDocument.ManyData[0].Id.Should().Be(animals[2].StringId); } [Fact] From a8d296f40da6204be5d251acde89366a16aff6c0 Mon Sep 17 00:00:00 2001 From: maurei Date: Mon, 28 Sep 2020 12:32:11 +0200 Subject: [PATCH 12/20] chore: rename tests --- .../ResourceInheritance/Models/Animal.cs | 3 + .../ResourceInheritance/Models/Person.cs | 2 +- .../ResourceInheritanceTests.cs | 92 ++++++++++++------- 3 files changed, 62 insertions(+), 35 deletions(-) diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Animal.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Animal.cs index 5dd6ac0613..7e40dc2bbb 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Animal.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Animal.cs @@ -7,5 +7,8 @@ public abstract class Animal : Identifiable { [Attr] public bool Feline { get; set; } + + [Attr] + public bool IsDomesticated { get; set; } } } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Person.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Person.cs index c36553905b..32b7f41717 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Person.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Person.cs @@ -8,7 +8,7 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance public abstract class Person : Identifiable { [Attr] - public string Name { get; set; } + public bool Retired { get; set; } [HasOne] public Animal Pet { get; set; } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceTests.cs index bac56839fc..368200862e 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceTests.cs @@ -37,7 +37,55 @@ public ResourceInheritanceTests( } [Fact] - public async Task When_including_to_one_relationship_should_be_successful() + public async Task Can_filter_on_base_attribute_in_primary_resource() + { + // Arrange + var animals = new List { new Cat { Meows = false }, new Cat { Meows = true }, new Dog() }; + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + await dbContext.AddRangeAsync(animals); + await dbContext.SaveChangesAsync(); + }); + + var route = "/animals?filter=equals(feline,'false')"; + + // Act + var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); + + responseDocument.ManyData.Should().HaveCount(1); + responseDocument.ManyData[0].Id.Should().Be(animals[2].StringId); + } + + [Fact] + public async Task Can_apply_sparse_field_selection_on_base_attributes_in_primary_resource() + { + // Arrange + var animals = new List { new Cat { IsDomesticated = true }, new Dog { Feline = false } }; + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + await dbContext.AddRangeAsync(animals); + await dbContext.SaveChangesAsync(); + }); + + var route = "/animals?fields=feline"; + + // Act + var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); + + responseDocument.ManyData.Should().HaveCount(2); + responseDocument.ManyData[0].Attributes.Should().HaveCount(2); + } + + [Fact] + public async Task Can_include_to_one_relationship() { // Arrange var person = new Male() @@ -66,7 +114,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => } [Fact] - public async Task When_including_to_many_relationship_should_be_successful() + public async Task Can_include_to_many_relationship() { // Arrange var person = new Male() @@ -98,33 +146,9 @@ await _testContext.RunOnDatabaseAsync(async dbContext => responseDocument.ManyData[0].Relationships["parents"].ManyData[1].Id.Should().Be(person.Parents[1].StringId); responseDocument.ManyData[0].Relationships["parents"].ManyData[1].Type.Should().Be("females"); } - - [Fact] - public async Task Can_filter_in_primary_resources() - { - // Arrange - var animals = new List { new Cat { Meows = false }, new Cat { Meows = true }, new Dog() }; - - await _testContext.RunOnDatabaseAsync(async dbContext => - { - await dbContext.AddRangeAsync(animals); - await dbContext.SaveChangesAsync(); - }); - - var route = "/animals?filter=equals(feline,'false')"; - - // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); - - responseDocument.ManyData.Should().HaveCount(1); - responseDocument.ManyData[0].Id.Should().Be(animals[2].StringId); - } - + [Fact] - public async Task When_including_many_to_many_relationship_should_be_successful() + public async Task Can_include_many_to_many_relationship() { // Arrange var person = new Male() @@ -160,7 +184,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => } [Fact] - public async Task When_creating_resource_with_to_one_relationship_should_be_successful() + public async Task Can_create_resource_with_to_one_relationship() { // Arrange var cat = new Cat(); @@ -207,7 +231,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => [Fact] - public async Task When_patching_resource_with_to_one_relationship_through_relationship_link_should_be_successful() + public async Task Can_patch_resource_with_to_one_relationship_through_relationship_link() { // Arrange var person = new Male(); @@ -244,7 +268,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => [Fact] - public async Task When_creating_resource_with_to_many_relationship_should_be_successful() + public async Task Can_create_resource_with_to_many_relationship() { // Arrange var father = new Male(); @@ -297,7 +321,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => } [Fact] - public async Task When_patching_resource_with_to_many_relationship_through_relationship_link_should_be_successful() + public async Task Can_patch_resource_with_to_many_relationship_through_relationship_link() { // Arrange var child = new Male(); @@ -339,7 +363,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => } [Fact] - public async Task When_creating_resource_with_many_to_many_relationship_should_be_successful() + public async Task Can_create_resource_with_many_to_many_relationship() { // Arrange var fiction = new FictionBook(); @@ -392,7 +416,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => } [Fact] - public async Task When_patching_resource_with_many_to_many_relationship_through_relationship_link_should_be_successful() + public async Task Can_patch_resource_with_many_to_many_relationship_through_relationship_link() { // Arrange var fiction = new FictionBook(); From 1ded442dc66b4177c067dd6fe912bdbf4f8870a2 Mon Sep 17 00:00:00 2001 From: maurei Date: Mon, 28 Sep 2020 12:43:38 +0200 Subject: [PATCH 13/20] chore: ignore tests for unsupported features --- .../ResourceInheritanceTests.cs | 246 +++++++++--------- 1 file changed, 123 insertions(+), 123 deletions(-) diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceTests.cs index 368200862e..2e71fd8102 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceTests.cs @@ -60,129 +60,6 @@ await _testContext.RunOnDatabaseAsync(async dbContext => responseDocument.ManyData[0].Id.Should().Be(animals[2].StringId); } - [Fact] - public async Task Can_apply_sparse_field_selection_on_base_attributes_in_primary_resource() - { - // Arrange - var animals = new List { new Cat { IsDomesticated = true }, new Dog { Feline = false } }; - - await _testContext.RunOnDatabaseAsync(async dbContext => - { - await dbContext.AddRangeAsync(animals); - await dbContext.SaveChangesAsync(); - }); - - var route = "/animals?fields=feline"; - - // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); - - responseDocument.ManyData.Should().HaveCount(2); - responseDocument.ManyData[0].Attributes.Should().HaveCount(2); - } - - [Fact] - public async Task Can_include_to_one_relationship() - { - // Arrange - var person = new Male() - { - Pet = new Cat { Meows = true } - }; - - await _testContext.RunOnDatabaseAsync(async dbContext => - { - await dbContext.AddAsync(person); - await dbContext.SaveChangesAsync(); - }); - - var route = "/people?include=pet"; - - // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); - - responseDocument.ManyData.Should().HaveCount(1); - responseDocument.ManyData[0].Relationships["pet"].HasResource.Should().BeTrue(); - responseDocument.ManyData[0].Relationships["pet"].SingleData.Type.Should().Be("cats"); - responseDocument.ManyData[0].Relationships["pet"].SingleData.Id.Should().Be(person.Pet.StringId); - } - - [Fact] - public async Task Can_include_to_many_relationship() - { - // Arrange - var person = new Male() - { - Parents = new List { new Male(), new Female() }, - }; - - await _testContext.RunOnDatabaseAsync(async dbContext => - { - await dbContext.AddAsync(person); - await dbContext.SaveChangesAsync(); - }); - - var route = "/people?include=parents"; - - // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); - - responseDocument.ManyData.Should().HaveCount(3); - responseDocument.ManyData[0].Relationships["parents"].HasResource.Should().BeTrue(); - responseDocument.ManyData[1].Relationships["parents"].HasResource.Should().BeFalse(); - responseDocument.ManyData[2].Relationships["parents"].HasResource.Should().BeFalse(); - responseDocument.ManyData[0].Relationships["parents"].ManyData.Should().HaveCount(2); - responseDocument.ManyData[0].Relationships["parents"].ManyData[0].Id.Should().Be(person.Parents[0].StringId); - responseDocument.ManyData[0].Relationships["parents"].ManyData[0].Type.Should().Be("males"); - responseDocument.ManyData[0].Relationships["parents"].ManyData[1].Id.Should().Be(person.Parents[1].StringId); - responseDocument.ManyData[0].Relationships["parents"].ManyData[1].Type.Should().Be("females"); - } - - [Fact] - public async Task Can_include_many_to_many_relationship() - { - // Arrange - var person = new Male() - { - PersonLiterature = new List - { - new LiteraturePerson { Literature = new FictionBook() }, - new LiteraturePerson { Literature = new NonFictionBook() } - } - }; - - await _testContext.RunOnDatabaseAsync(async dbContext => - { - await dbContext.AddAsync(person); - await dbContext.SaveChangesAsync(); - }); - - var route = "/people?include=favoriteLiterature"; - - // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); - - responseDocument.ManyData.Should().HaveCount(1); - responseDocument.ManyData[0].Relationships["favoriteLiterature"].HasResource.Should().BeTrue(); - responseDocument.ManyData[0].Relationships["favoriteLiterature"].ManyData.Should().HaveCount(2); - responseDocument.ManyData[0].Relationships["favoriteLiterature"].ManyData[0].Id.Should().Be(person.PersonLiterature[0].Literature.StringId); - responseDocument.ManyData[0].Relationships["favoriteLiterature"].ManyData[0].Type.Should().Be("fictionBooks"); - responseDocument.ManyData[0].Relationships["favoriteLiterature"].ManyData[1].Id.Should().Be(person.PersonLiterature[1].Literature.StringId); - responseDocument.ManyData[0].Relationships["favoriteLiterature"].ManyData[1].Type.Should().Be("nonFictionBooks"); - } - [Fact] public async Task Can_create_resource_with_to_one_relationship() { @@ -458,5 +335,128 @@ await _testContext.RunOnDatabaseAsync(async dbContext => favoriteLiterature.Should().ContainSingle(p => p is NonFictionBook); }); } + + [Fact(Skip = "Resource inheritance is currently not fully supported, see https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/844")] + public async Task Can_apply_sparse_field_selection_on_base_attributes_in_primary_resource() + { + // Arrange + var animals = new List { new Cat { IsDomesticated = true }, new Dog { Feline = false } }; + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + await dbContext.AddRangeAsync(animals); + await dbContext.SaveChangesAsync(); + }); + + var route = "/animals?fields=feline"; + + // Act + var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); + + responseDocument.ManyData.Should().HaveCount(2); + responseDocument.ManyData[0].Attributes.Should().HaveCount(2); + } + + [Fact(Skip = "Resource inheritance is currently not fully supported, see https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/844")] + public async Task Can_include_to_one_relationship() + { + // Arrange + var person = new Male() + { + Pet = new Cat { Meows = true } + }; + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + await dbContext.AddAsync(person); + await dbContext.SaveChangesAsync(); + }); + + var route = "/people?include=pet"; + + // Act + var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); + + responseDocument.ManyData.Should().HaveCount(1); + responseDocument.ManyData[0].Relationships["pet"].HasResource.Should().BeTrue(); + responseDocument.ManyData[0].Relationships["pet"].SingleData.Type.Should().Be("cats"); + responseDocument.ManyData[0].Relationships["pet"].SingleData.Id.Should().Be(person.Pet.StringId); + } + + [Fact(Skip = "Resource inheritance is currently not fully supported, see https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/844")] + public async Task Can_include_to_many_relationship() + { + // Arrange + var person = new Male() + { + Parents = new List { new Male(), new Female() }, + }; + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + await dbContext.AddAsync(person); + await dbContext.SaveChangesAsync(); + }); + + var route = "/people?include=parents"; + + // Act + var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); + + responseDocument.ManyData.Should().HaveCount(3); + responseDocument.ManyData[0].Relationships["parents"].HasResource.Should().BeTrue(); + responseDocument.ManyData[1].Relationships["parents"].HasResource.Should().BeFalse(); + responseDocument.ManyData[2].Relationships["parents"].HasResource.Should().BeFalse(); + responseDocument.ManyData[0].Relationships["parents"].ManyData.Should().HaveCount(2); + responseDocument.ManyData[0].Relationships["parents"].ManyData[0].Id.Should().Be(person.Parents[0].StringId); + responseDocument.ManyData[0].Relationships["parents"].ManyData[0].Type.Should().Be("males"); + responseDocument.ManyData[0].Relationships["parents"].ManyData[1].Id.Should().Be(person.Parents[1].StringId); + responseDocument.ManyData[0].Relationships["parents"].ManyData[1].Type.Should().Be("females"); + } + + [Fact(Skip = "Resource inheritance is currently not fully supported, see https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/844")] + public async Task Can_include_many_to_many_relationship() + { + // Arrange + var person = new Male() + { + PersonLiterature = new List + { + new LiteraturePerson { Literature = new FictionBook() }, + new LiteraturePerson { Literature = new NonFictionBook() } + } + }; + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + await dbContext.AddAsync(person); + await dbContext.SaveChangesAsync(); + }); + + var route = "/people?include=favoriteLiterature"; + + // Act + var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); + + responseDocument.ManyData.Should().HaveCount(1); + responseDocument.ManyData[0].Relationships["favoriteLiterature"].HasResource.Should().BeTrue(); + responseDocument.ManyData[0].Relationships["favoriteLiterature"].ManyData.Should().HaveCount(2); + responseDocument.ManyData[0].Relationships["favoriteLiterature"].ManyData[0].Id.Should().Be(person.PersonLiterature[0].Literature.StringId); + responseDocument.ManyData[0].Relationships["favoriteLiterature"].ManyData[0].Type.Should().Be("fictionBooks"); + responseDocument.ManyData[0].Relationships["favoriteLiterature"].ManyData[1].Id.Should().Be(person.PersonLiterature[1].Literature.StringId); + responseDocument.ManyData[0].Relationships["favoriteLiterature"].ManyData[1].Type.Should().Be("nonFictionBooks"); + } } } From 03cfb073d8f026c18a739eddb4c209a30894963d Mon Sep 17 00:00:00 2001 From: maurei Date: Mon, 28 Sep 2020 13:56:51 +0200 Subject: [PATCH 14/20] fix: inheritance support in response deserializer --- .../Client/Internal/ResponseDeserializer.cs | 11 ++-- .../ResourceInheritance/Models/Female.cs | 6 +- .../ResourceInheritance/Models/Male.cs | 6 +- .../Client/ResponseDeserializerTests.cs | 59 ++++++++++++++++++- .../Serialization/DeserializerTestsSetup.cs | 6 +- .../SerializationTestsSetupBase.cs | 10 ++++ test/UnitTests/TestModels.cs | 1 - test/UnitTests/UnitTests.csproj | 1 + 8 files changed, 86 insertions(+), 14 deletions(-) diff --git a/src/JsonApiDotNetCore/Serialization/Client/Internal/ResponseDeserializer.cs b/src/JsonApiDotNetCore/Serialization/Client/Internal/ResponseDeserializer.cs index 277cd0b2c0..2fd8126582 100644 --- a/src/JsonApiDotNetCore/Serialization/Client/Internal/ResponseDeserializer.cs +++ b/src/JsonApiDotNetCore/Serialization/Client/Internal/ResponseDeserializer.cs @@ -87,19 +87,20 @@ protected override void AfterProcessField(IIdentifiable resource, ResourceFieldA /// private IIdentifiable ParseIncludedRelationship(RelationshipAttribute relationshipAttr, ResourceIdentifierObject relatedResourceIdentifier) { - var relatedInstance = (IIdentifiable)ResourceFactory.CreateInstance(relationshipAttr.RightType); + var relatedResourceContext = ResourceContextProvider.GetResourceContext(relatedResourceIdentifier.Type); + + var relatedInstance = (IIdentifiable)ResourceFactory.CreateInstance(relatedResourceContext.ResourceType); relatedInstance.StringId = relatedResourceIdentifier.Id; var includedResource = GetLinkedResource(relatedResourceIdentifier); if (includedResource == null) return relatedInstance; - var resourceContext = ResourceContextProvider.GetResourceContext(relatedResourceIdentifier.Type); - if (resourceContext == null) + if (relatedResourceContext == null) throw new InvalidOperationException($"Included type '{relationshipAttr.RightType}' is not a registered json:api resource."); - SetAttributes(relatedInstance, includedResource.Attributes, resourceContext.Attributes); - SetRelationships(relatedInstance, includedResource.Relationships, resourceContext.Relationships); + SetAttributes(relatedInstance, includedResource.Attributes, relatedResourceContext.Attributes); + SetRelationships(relatedInstance, includedResource.Relationships, relatedResourceContext.Relationships); return relatedInstance; } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Female.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Female.cs index 87062b4d6c..d48d0b1106 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Female.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Female.cs @@ -2,5 +2,9 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance { - public sealed class Female : Person { } + public sealed class Female : Person + { + [Attr] + public bool IsPregnant { get; set; } + } } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Male.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Male.cs index ca6067b7d0..302ed0f20e 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Male.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Male.cs @@ -2,5 +2,9 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance { - public sealed class Male : Person { } + public sealed class Male : Person + { + [Attr] + public bool HasBeard { get; set; } + } } diff --git a/test/UnitTests/Serialization/Client/ResponseDeserializerTests.cs b/test/UnitTests/Serialization/Client/ResponseDeserializerTests.cs index c5d2d1d6ce..8159fb44d9 100644 --- a/test/UnitTests/Serialization/Client/ResponseDeserializerTests.cs +++ b/test/UnitTests/Serialization/Client/ResponseDeserializerTests.cs @@ -4,6 +4,7 @@ using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Serialization.Client.Internal; using JsonApiDotNetCore.Serialization.Objects; +using JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance; using Newtonsoft.Json; using UnitTests.TestModels; using Xunit; @@ -231,8 +232,7 @@ public void DeserializeSingle_NestedIncluded_CanDeserialize() var nestedIncludedResource = includedResource.Principal; Assert.Equal(nestedIncludeAttributeValue, nestedIncludedResource.AttributeMember); } - - + [Fact] public void DeserializeSingle_DeeplyNestedIncluded_CanDeserialize() { @@ -284,7 +284,6 @@ public void DeserializeSingle_DeeplyNestedIncluded_CanDeserialize() Assert.Equal(deeplyNestedIncludedAttributeValue, deeplyNestedIncluded.AttributeMember); } - [Fact] public void DeserializeList_DeeplyNestedIncluded_CanDeserialize() { @@ -335,5 +334,59 @@ public void DeserializeList_DeeplyNestedIncluded_CanDeserialize() Assert.Equal(10, deeplyNestedIncluded.Id); Assert.Equal(deeplyNestedIncludedAttributeValue, deeplyNestedIncluded.AttributeMember); } + + [Fact] + public void DeserializeSingle_ResourceWithInheritanceAndInclusions_CanDeserialize() + { + // Arrange + var content = CreateDocumentWithRelationships("males"); + content.SingleData.Relationships.Add("parents", CreateRelationshipData("males", isToManyData:true, id: "10")); + content.SingleData.Relationships["parents"].ManyData.Add(CreateRelationshipData("females", id: "10").SingleData); + content.SingleData.Relationships.Add("pet", CreateRelationshipData("cats", id: "20")); + content.Included = new List + { + new ResourceObject + { + Type = "males", + Id = "10", + Attributes = new Dictionary { { "hasBeard", "false" }, { "retired", "true" } } + }, + new ResourceObject + { + Type = "females", + Id = "11", + Attributes = new Dictionary { { "isPregnant", "false" }, { "retired", "false" } } + }, + new ResourceObject + { + Type = "cats", + Id = "20", + Attributes = new Dictionary { {"feline", "true" }, { "meows", "true" } } + }, + }; + var body = JsonConvert.SerializeObject(content); + + // Act + var result = _deserializer.DeserializeSingle(body); + var resource = result.Data; + + // Assert + Assert.Equal(1, resource.Id); + Assert.NotNull(resource.Pet); + Assert.True(resource.Pet.Feline); + Assert.True(resource.Pet is Cat); + Assert.True(((Cat)resource.Pet).Meows); + + Assert.NotEmpty(resource.Parents); + var father = resource.Parents[0]; + Assert.True(father is Male); + Assert.True(father.Retired); + Assert.False(((Male)father).HasBeard); + + var mother = resource.Parents[1]; + Assert.True(mother is Female); + Assert.False(mother.Retired); + Assert.False(((Female)mother).IsPregnant); + } } } diff --git a/test/UnitTests/Serialization/DeserializerTestsSetup.cs b/test/UnitTests/Serialization/DeserializerTestsSetup.cs index 95579b3f9f..3940f4fbc7 100644 --- a/test/UnitTests/Serialization/DeserializerTestsSetup.cs +++ b/test/UnitTests/Serialization/DeserializerTestsSetup.cs @@ -50,14 +50,14 @@ protected Document CreateDocumentWithRelationships(string primaryType) }; } - protected RelationshipEntry CreateRelationshipData(string relatedType = null, bool isToManyData = false) + protected RelationshipEntry CreateRelationshipData(string relatedType = null, bool isToManyData = false, string id = "10") { var data = new RelationshipEntry(); - var rio = relatedType == null ? null : new ResourceIdentifierObject { Id = "10", Type = relatedType }; + var rio = relatedType == null ? null : new ResourceIdentifierObject { Id = id, Type = relatedType }; if (isToManyData) { - data.Data = new List(); + data.Data = new List(); if (relatedType != null) ((List)data.Data).Add(rio); } else diff --git a/test/UnitTests/Serialization/SerializationTestsSetupBase.cs b/test/UnitTests/Serialization/SerializationTestsSetupBase.cs index ad51859960..5ca4c13d16 100644 --- a/test/UnitTests/Serialization/SerializationTestsSetupBase.cs +++ b/test/UnitTests/Serialization/SerializationTestsSetupBase.cs @@ -1,7 +1,9 @@ using Bogus; using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance; using Microsoft.Extensions.Logging.Abstractions; using UnitTests.TestModels; +using InheritancePerson = JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance.Person; using Person = UnitTests.TestModels.Person; namespace UnitTests.Serialization @@ -57,6 +59,14 @@ protected IResourceGraph BuildGraph() resourceGraphBuilder.Add(); resourceGraphBuilder.Add(); resourceGraphBuilder.Add(); + + resourceGraphBuilder.Add(); + resourceGraphBuilder.Add(); + resourceGraphBuilder.Add(); + + resourceGraphBuilder.Add(); + resourceGraphBuilder.Add(); + resourceGraphBuilder.Add(); return resourceGraphBuilder.Build(); } diff --git a/test/UnitTests/TestModels.cs b/test/UnitTests/TestModels.cs index efde2004b9..20bcbbed45 100644 --- a/test/UnitTests/TestModels.cs +++ b/test/UnitTests/TestModels.cs @@ -119,5 +119,4 @@ public class Song : Identifiable { [Attr] public string Title { get; set; } } - } diff --git a/test/UnitTests/UnitTests.csproj b/test/UnitTests/UnitTests.csproj index 7d4cc30981..72fc546d3e 100644 --- a/test/UnitTests/UnitTests.csproj +++ b/test/UnitTests/UnitTests.csproj @@ -11,6 +11,7 @@ + From 0c7f327369b24ed02482e6bbfcf7ae153ea5acf0 Mon Sep 17 00:00:00 2001 From: maurei Date: Mon, 28 Sep 2020 14:20:24 +0200 Subject: [PATCH 15/20] fix: overlapping tests --- .../LinksWithoutNamespaceTests.cs | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/LinksWithoutNamespaceTests.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/LinksWithoutNamespaceTests.cs index 6b8d1d970b..8fb61f77e8 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/LinksWithoutNamespaceTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/LinksWithoutNamespaceTests.cs @@ -2,13 +2,16 @@ using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Serialization.Objects; +using JsonApiDotNetCore.Services; using JsonApiDotNetCoreExample; using JsonApiDotNetCoreExample.Data; using JsonApiDotNetCoreExample.Models; using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using Xunit; namespace JsonApiDotNetCoreExampleTests.Acceptance.Spec.DocumentTests @@ -29,16 +32,16 @@ public async Task GET_RelativeLinks_True_Without_Namespace_Returns_RelativeLinks var options = (JsonApiOptions) _testContext.Factory.Services.GetRequiredService(); options.UseRelativeLinks = true; - var person = new Person(); + var blog = new Blog(); await _testContext.RunOnDatabaseAsync(async dbContext => { - dbContext.People.Add(person); + dbContext.Blogs.Add(blog); await dbContext.SaveChangesAsync(); }); - var route = "/people/" + person.StringId; + var route = "/blogs/" + blog.StringId; // Act var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); @@ -46,7 +49,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); - responseDocument.Links.Self.Should().Be("/people/" + person.StringId); + responseDocument.Links.Self.Should().Be("/blogs/" + blog.StringId); } [Fact] @@ -56,16 +59,16 @@ public async Task GET_RelativeLinks_False_Without_Namespace_Returns_AbsoluteLink var options = (JsonApiOptions) _testContext.Factory.Services.GetRequiredService(); options.UseRelativeLinks = false; - var person = new Person(); + var blog = new Blog(); await _testContext.RunOnDatabaseAsync(async dbContext => { - dbContext.People.Add(person); + dbContext.Blogs.Add(blog); await dbContext.SaveChangesAsync(); }); - var route = "/people/" + person.StringId; + var route = "/blogs/" + blog.StringId; // Act var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); @@ -73,7 +76,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); - responseDocument.Links.Self.Should().Be("http://localhost/people/" + person.StringId); + responseDocument.Links.Self.Should().Be("http://localhost/blogs/" + blog.StringId); } } @@ -90,4 +93,14 @@ protected override void ConfigureJsonApiOptions(JsonApiOptions options) options.Namespace = null; } } + + public sealed class BlogsController : JsonApiController + { + public BlogsController( + IJsonApiOptions options, + ILoggerFactory loggerFactory, + IResourceService resourceService) + : base(options, loggerFactory, resourceService) + { } + } } From 6c76785f3461255e17140e08bc95250be88c722e Mon Sep 17 00:00:00 2001 From: maurei Date: Mon, 28 Sep 2020 16:28:48 +0200 Subject: [PATCH 16/20] fix: white space --- test/UnitTests/Serialization/DeserializerTestsSetup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/UnitTests/Serialization/DeserializerTestsSetup.cs b/test/UnitTests/Serialization/DeserializerTestsSetup.cs index 3940f4fbc7..2f8d4a053f 100644 --- a/test/UnitTests/Serialization/DeserializerTestsSetup.cs +++ b/test/UnitTests/Serialization/DeserializerTestsSetup.cs @@ -57,7 +57,7 @@ protected RelationshipEntry CreateRelationshipData(string relatedType = null, bo if (isToManyData) { - data.Data = new List(); + data.Data = new List(); if (relatedType != null) ((List)data.Data).Add(rio); } else From 753a5bfc216c22c9b6f65f7ff480f3e3b34aa92a Mon Sep 17 00:00:00 2001 From: maurei Date: Tue, 29 Sep 2020 11:23:39 +0200 Subject: [PATCH 17/20] chore: review --- .../Resources/ResourceFactory.cs | 4 ++-- .../Client/Internal/ResponseDeserializer.cs | 24 +++++++++++-------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/JsonApiDotNetCore/Resources/ResourceFactory.cs b/src/JsonApiDotNetCore/Resources/ResourceFactory.cs index 4502939f5b..984ad524e3 100644 --- a/src/JsonApiDotNetCore/Resources/ResourceFactory.cs +++ b/src/JsonApiDotNetCore/Resources/ResourceFactory.cs @@ -119,9 +119,9 @@ private static ConstructorInfo GetLongestConstructor(Type type) if (constructors.Length == 0) { - throw new InvalidOperationException($"No public constructors found for '{type.FullName}'."); + throw new InvalidOperationException($"No public constructor was found for '{type.FullName}'."); } - + ConstructorInfo bestMatch = constructors[0]; int maxParameterLength = constructors[0].GetParameters().Length; diff --git a/src/JsonApiDotNetCore/Serialization/Client/Internal/ResponseDeserializer.cs b/src/JsonApiDotNetCore/Serialization/Client/Internal/ResponseDeserializer.cs index 2fd8126582..90f1f35a8f 100644 --- a/src/JsonApiDotNetCore/Serialization/Client/Internal/ResponseDeserializer.cs +++ b/src/JsonApiDotNetCore/Serialization/Client/Internal/ResponseDeserializer.cs @@ -72,11 +72,11 @@ protected override void AfterProcessField(IIdentifiable resource, ResourceFieldA { // add attributes and relationships of a parsed HasOne relationship var rio = data.SingleData; - hasOneAttr.SetValue(resource, rio == null ? null : ParseIncludedRelationship(hasOneAttr, rio), ResourceFactory); + hasOneAttr.SetValue(resource, rio == null ? null : ParseIncludedRelationship(rio), ResourceFactory); } else if (field is HasManyAttribute hasManyAttr) { // add attributes and relationships of a parsed HasMany relationship - var items = data.ManyData.Select(rio => ParseIncludedRelationship(hasManyAttr, rio)); + var items = data.ManyData.Select(rio => ParseIncludedRelationship(rio)); var values = TypeHelper.CopyToTypedCollection(items, hasManyAttr.Property.PropertyType); hasManyAttr.SetValue(resource, values, ResourceFactory); } @@ -85,22 +85,26 @@ protected override void AfterProcessField(IIdentifiable resource, ResourceFieldA /// /// Searches for and parses the included relationship. /// - private IIdentifiable ParseIncludedRelationship(RelationshipAttribute relationshipAttr, ResourceIdentifierObject relatedResourceIdentifier) + private IIdentifiable ParseIncludedRelationship(ResourceIdentifierObject relatedResourceIdentifier) { var relatedResourceContext = ResourceContextProvider.GetResourceContext(relatedResourceIdentifier.Type); + + if (relatedResourceContext == null) + { + throw new InvalidOperationException($"Included type '{relatedResourceIdentifier.Type}' is not a registered json:api resource."); + } var relatedInstance = (IIdentifiable)ResourceFactory.CreateInstance(relatedResourceContext.ResourceType); relatedInstance.StringId = relatedResourceIdentifier.Id; var includedResource = GetLinkedResource(relatedResourceIdentifier); - if (includedResource == null) - return relatedInstance; - if (relatedResourceContext == null) - throw new InvalidOperationException($"Included type '{relationshipAttr.RightType}' is not a registered json:api resource."); - - SetAttributes(relatedInstance, includedResource.Attributes, relatedResourceContext.Attributes); - SetRelationships(relatedInstance, includedResource.Relationships, relatedResourceContext.Relationships); + if (includedResource != null) + { + SetAttributes(relatedInstance, includedResource.Attributes, relatedResourceContext.Attributes); + SetRelationships(relatedInstance, includedResource.Relationships, relatedResourceContext.Relationships); + } + return relatedInstance; } From 431d8aa3bceb456dbe0c4420edec49a80ed3d396 Mon Sep 17 00:00:00 2001 From: maurei Date: Tue, 29 Sep 2020 14:14:52 +0200 Subject: [PATCH 18/20] chore: remove suggestive tests --- .../ResourceInheritanceTests.cs | 153 +----------------- 1 file changed, 3 insertions(+), 150 deletions(-) diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceTests.cs index 2e71fd8102..7f3c1fb3c0 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/ResourceInheritanceTests.cs @@ -36,30 +36,6 @@ public ResourceInheritanceTests( }).Wait(); } - [Fact] - public async Task Can_filter_on_base_attribute_in_primary_resource() - { - // Arrange - var animals = new List { new Cat { Meows = false }, new Cat { Meows = true }, new Dog() }; - - await _testContext.RunOnDatabaseAsync(async dbContext => - { - await dbContext.AddRangeAsync(animals); - await dbContext.SaveChangesAsync(); - }); - - var route = "/animals?filter=equals(feline,'false')"; - - // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); - - responseDocument.ManyData.Should().HaveCount(1); - responseDocument.ManyData[0].Id.Should().Be(animals[2].StringId); - } - [Fact] public async Task Can_create_resource_with_to_one_relationship() { @@ -83,7 +59,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => { "pet", new { - data = new {type = "cats", id = cat.StringId} + data = new { type = "cats", id = cat.StringId } } } } @@ -124,7 +100,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => var requestBody = new { - data = new {type = "cats", id = cat.StringId} + data = new { type = "cats", id = cat.StringId } }; // Act @@ -275,7 +251,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => }; // Act - var (httpResponse, responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); + var (_, responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); // Assert await _testContext.RunOnDatabaseAsync(async dbContext => @@ -335,128 +311,5 @@ await _testContext.RunOnDatabaseAsync(async dbContext => favoriteLiterature.Should().ContainSingle(p => p is NonFictionBook); }); } - - [Fact(Skip = "Resource inheritance is currently not fully supported, see https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/844")] - public async Task Can_apply_sparse_field_selection_on_base_attributes_in_primary_resource() - { - // Arrange - var animals = new List { new Cat { IsDomesticated = true }, new Dog { Feline = false } }; - - await _testContext.RunOnDatabaseAsync(async dbContext => - { - await dbContext.AddRangeAsync(animals); - await dbContext.SaveChangesAsync(); - }); - - var route = "/animals?fields=feline"; - - // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); - - responseDocument.ManyData.Should().HaveCount(2); - responseDocument.ManyData[0].Attributes.Should().HaveCount(2); - } - - [Fact(Skip = "Resource inheritance is currently not fully supported, see https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/844")] - public async Task Can_include_to_one_relationship() - { - // Arrange - var person = new Male() - { - Pet = new Cat { Meows = true } - }; - - await _testContext.RunOnDatabaseAsync(async dbContext => - { - await dbContext.AddAsync(person); - await dbContext.SaveChangesAsync(); - }); - - var route = "/people?include=pet"; - - // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); - - responseDocument.ManyData.Should().HaveCount(1); - responseDocument.ManyData[0].Relationships["pet"].HasResource.Should().BeTrue(); - responseDocument.ManyData[0].Relationships["pet"].SingleData.Type.Should().Be("cats"); - responseDocument.ManyData[0].Relationships["pet"].SingleData.Id.Should().Be(person.Pet.StringId); - } - - [Fact(Skip = "Resource inheritance is currently not fully supported, see https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/844")] - public async Task Can_include_to_many_relationship() - { - // Arrange - var person = new Male() - { - Parents = new List { new Male(), new Female() }, - }; - - await _testContext.RunOnDatabaseAsync(async dbContext => - { - await dbContext.AddAsync(person); - await dbContext.SaveChangesAsync(); - }); - - var route = "/people?include=parents"; - - // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); - - responseDocument.ManyData.Should().HaveCount(3); - responseDocument.ManyData[0].Relationships["parents"].HasResource.Should().BeTrue(); - responseDocument.ManyData[1].Relationships["parents"].HasResource.Should().BeFalse(); - responseDocument.ManyData[2].Relationships["parents"].HasResource.Should().BeFalse(); - responseDocument.ManyData[0].Relationships["parents"].ManyData.Should().HaveCount(2); - responseDocument.ManyData[0].Relationships["parents"].ManyData[0].Id.Should().Be(person.Parents[0].StringId); - responseDocument.ManyData[0].Relationships["parents"].ManyData[0].Type.Should().Be("males"); - responseDocument.ManyData[0].Relationships["parents"].ManyData[1].Id.Should().Be(person.Parents[1].StringId); - responseDocument.ManyData[0].Relationships["parents"].ManyData[1].Type.Should().Be("females"); - } - - [Fact(Skip = "Resource inheritance is currently not fully supported, see https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/844")] - public async Task Can_include_many_to_many_relationship() - { - // Arrange - var person = new Male() - { - PersonLiterature = new List - { - new LiteraturePerson { Literature = new FictionBook() }, - new LiteraturePerson { Literature = new NonFictionBook() } - } - }; - - await _testContext.RunOnDatabaseAsync(async dbContext => - { - await dbContext.AddAsync(person); - await dbContext.SaveChangesAsync(); - }); - - var route = "/people?include=favoriteLiterature"; - - // Act - var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); - - responseDocument.ManyData.Should().HaveCount(1); - responseDocument.ManyData[0].Relationships["favoriteLiterature"].HasResource.Should().BeTrue(); - responseDocument.ManyData[0].Relationships["favoriteLiterature"].ManyData.Should().HaveCount(2); - responseDocument.ManyData[0].Relationships["favoriteLiterature"].ManyData[0].Id.Should().Be(person.PersonLiterature[0].Literature.StringId); - responseDocument.ManyData[0].Relationships["favoriteLiterature"].ManyData[0].Type.Should().Be("fictionBooks"); - responseDocument.ManyData[0].Relationships["favoriteLiterature"].ManyData[1].Id.Should().Be(person.PersonLiterature[1].Literature.StringId); - responseDocument.ManyData[0].Relationships["favoriteLiterature"].ManyData[1].Type.Should().Be("nonFictionBooks"); - } } } From 1f393f692a55ab468b0f606cc6d1d25480b054c6 Mon Sep 17 00:00:00 2001 From: maurei Date: Tue, 29 Sep 2020 15:21:18 +0200 Subject: [PATCH 19/20] fix: more intuitive test models --- .../Controllers/FictionBooksController.cs | 4 +- .../Controllers/NonFictionBooksController.cs | 4 +- .../ResourceInheritance/Models/Book.cs | 10 +++ .../ResourceInheritance/Models/Cat.cs | 2 +- .../Models/{Literature.cs => Content.cs} | 2 +- .../{LiteraturePerson.cs => ContentPerson.cs} | 6 +- .../ResourceInheritance/Models/Dog.cs | 2 +- .../ResourceInheritance/Models/FictionBook.cs | 4 -- .../Models/NonFictionBook.cs | 4 -- .../ResourceInheritance/Models/Person.cs | 9 +-- .../ResourceInheritance/Models/Video.cs | 10 +++ .../ResourceInheritanceDbContext.cs | 14 ++--- .../ResourceInheritanceTests.cs | 62 +++++++++---------- 13 files changed, 71 insertions(+), 62 deletions(-) create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Book.cs rename test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/{Literature.cs => Content.cs} (67%) rename test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/{LiteraturePerson.cs => ContentPerson.cs} (58%) delete mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/FictionBook.cs delete mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/NonFictionBook.cs create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Models/Video.cs diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/FictionBooksController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/FictionBooksController.cs index f4a65a2a7d..bdfc051041 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/FictionBooksController.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/FictionBooksController.cs @@ -5,10 +5,10 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance { - public sealed class FictionBooksController : JsonApiController + public sealed class FictionBooksController : JsonApiController { public FictionBooksController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) + IResourceService resourceService) : base(options, loggerFactory, resourceService) { } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/NonFictionBooksController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/NonFictionBooksController.cs index ae6259d704..a575731b1e 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/NonFictionBooksController.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/ResourceInheritance/Controllers/NonFictionBooksController.cs @@ -5,10 +5,10 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.ResourceInheritance { - public sealed class NonFictionBooksController : JsonApiController + public sealed class NonFictionBooksController : JsonApiController