From 9283c7202308a91e1c1fd34f2fdae8eb28e2c948 Mon Sep 17 00:00:00 2001 From: Bart Koelman Date: Thu, 6 May 2021 12:23:04 +0200 Subject: [PATCH 1/3] Bug repro --- .../QueryableBuilding/SelectClauseBuilder.cs | 8 +- .../IntegrationTests/Issue988/DocumentType.cs | 14 ++ .../Issue988/DocumentTypesController.cs | 16 ++ .../IntegrationTests/Issue988/Engagement.cs | 37 +++ .../Issue988/EngagementPartiesController.cs | 16 ++ .../Issue988/EngagementParty.cs | 25 ++ .../EngagementPartyResourceDefinition.cs | 34 +++ .../Issue988/EngagementsController.cs | 16 ++ .../IntegrationTests/Issue988/EntityBase.cs | 18 ++ .../Issue988/IssueDbContext.cs | 18 ++ .../IntegrationTests/Issue988/IssueFakers.cs | 34 +++ .../IntegrationTests/Issue988/IssueTests.cs | 216 ++++++++++++++++++ .../Issue988/ModelConstants.cs | 10 + 13 files changed, 461 insertions(+), 1 deletion(-) create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/DocumentType.cs create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/DocumentTypesController.cs create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/Engagement.cs create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/EngagementPartiesController.cs create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/EngagementParty.cs create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/EngagementPartyResourceDefinition.cs create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/EngagementsController.cs create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/EntityBase.cs create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/IssueDbContext.cs create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/IssueFakers.cs create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/IssueTests.cs create mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/ModelConstants.cs diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/SelectClauseBuilder.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/SelectClauseBuilder.cs index b6ddf29c9a..be618d0af2 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/SelectClauseBuilder.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/QueryableBuilding/SelectClauseBuilder.cs @@ -133,7 +133,13 @@ private ICollection ToPropertySelectors(IDictionary + { + [Attr] + public string Description { get; set; } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/DocumentTypesController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/DocumentTypesController.cs new file mode 100644 index 0000000000..abb114b918 --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/DocumentTypesController.cs @@ -0,0 +1,16 @@ +using System; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Services; +using Microsoft.Extensions.Logging; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.Issue988 +{ + public sealed class DocumentTypesController : JsonApiController + { + public DocumentTypesController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) + : base(options, loggerFactory, resourceService) + { + } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/Engagement.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/Engagement.cs new file mode 100644 index 0000000000..bfb9216450 --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/Engagement.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using JetBrains.Annotations; +using JsonApiDotNetCore.Resources.Annotations; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.Issue988 +{ + [UsedImplicitly(ImplicitUseTargetFlags.Members)] + public sealed class Engagement : EntityBase + { + // Data + + [Attr] + public string Name { get; set; } + + // Navigation Properties + + [HasMany] + public ICollection DocumentTypes { get; set; } //= new List(); + + [HasMany] + [EagerLoad] + public ICollection Parties { get; set; } //= new List(); + + [HasMany] + [NotMapped] + public ICollection FirstParties => + Parties.Where(party => party.Role == ModelConstants.FirstPartyRoleName).OrderBy(party => party.ShortName).ToList(); + + [HasMany] + [NotMapped] + public ICollection SecondParties => + Parties.Where(party => party.Role == ModelConstants.SecondPartyRoleName).OrderBy(party => party.ShortName).ToList(); + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/EngagementPartiesController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/EngagementPartiesController.cs new file mode 100644 index 0000000000..8e7977c0ef --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/EngagementPartiesController.cs @@ -0,0 +1,16 @@ +using System; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Services; +using Microsoft.Extensions.Logging; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.Issue988 +{ + public sealed class EngagementPartiesController : JsonApiController + { + public EngagementPartiesController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) + : base(options, loggerFactory, resourceService) + { + } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/EngagementParty.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/EngagementParty.cs new file mode 100644 index 0000000000..3d7df26b5b --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/EngagementParty.cs @@ -0,0 +1,25 @@ +using System; +using JetBrains.Annotations; +using JsonApiDotNetCore.Resources.Annotations; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.Issue988 +{ + [UsedImplicitly(ImplicitUseTargetFlags.Members)] + public sealed class EngagementParty : EntityBase + { + // Data (simplified) + + [Attr] + public string Role { get; set; } + + [Attr] + public string ShortName { get; set; } + + // Foreign Keys (simplified) + + [HasOne] + public Engagement Engagement { get; set; } + + //public Guid EngagementId { get; set; } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/EngagementPartyResourceDefinition.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/EngagementPartyResourceDefinition.cs new file mode 100644 index 0000000000..c054d513f1 --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/EngagementPartyResourceDefinition.cs @@ -0,0 +1,34 @@ +using System; +using System.ComponentModel; +using JetBrains.Annotations; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Queries.Expressions; +using JsonApiDotNetCore.Resources; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.Issue988 +{ + [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] + public sealed class EngagementPartyResourceDefinition : JsonApiResourceDefinition + { + /// + public EngagementPartyResourceDefinition(IResourceGraph resourceGraph) + : base(resourceGraph) + { + } + + /// + public override SortExpression OnApplySort(SortExpression? existingSort) + { + if (existingSort != null) + { + return existingSort; + } + + return CreateSortExpressionFromLambda(new PropertySortOrder + { + (ep => ep.Role, ListSortDirection.Ascending), + (ep => ep.ShortName, ListSortDirection.Ascending) + }); + } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/EngagementsController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/EngagementsController.cs new file mode 100644 index 0000000000..57d98abacc --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/EngagementsController.cs @@ -0,0 +1,16 @@ +using System; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Services; +using Microsoft.Extensions.Logging; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.Issue988 +{ + public sealed class EngagementsController : JsonApiController + { + public EngagementsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) + : base(options, loggerFactory, resourceService) + { + } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/EntityBase.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/EntityBase.cs new file mode 100644 index 0000000000..2bbdc72724 --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/EntityBase.cs @@ -0,0 +1,18 @@ +using System; +using System.ComponentModel.DataAnnotations; +using JetBrains.Annotations; +using JsonApiDotNetCore.Resources; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.Issue988 +{ + [UsedImplicitly(ImplicitUseTargetFlags.Members)] + public abstract class EntityBase : Identifiable + { + public DateTimeOffset? DateCreated { get; set; } + + public DateTimeOffset? DateModified { get; set; } + + [Timestamp] + public byte[] RowVersion { get; set; } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/IssueDbContext.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/IssueDbContext.cs new file mode 100644 index 0000000000..8eb8490c54 --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/IssueDbContext.cs @@ -0,0 +1,18 @@ +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.Issue988 +{ + [UsedImplicitly(ImplicitUseTargetFlags.Members)] + public sealed class IssueDbContext : DbContext + { + public DbSet Engagements { get; set; } + public DbSet EngagementParties { get; set; } + public DbSet DocumentTypes { get; set; } + + public IssueDbContext(DbContextOptions options) + : base(options) + { + } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/IssueFakers.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/IssueFakers.cs new file mode 100644 index 0000000000..c1a227bd21 --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/IssueFakers.cs @@ -0,0 +1,34 @@ +using System; +using Bogus; +using JetBrains.Annotations; +using TestBuildingBlocks; + +// @formatter:wrap_chained_method_calls chop_always +// @formatter:keep_existing_linebreaks true + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.Issue988 +{ + [UsedImplicitly(ImplicitUseTargetFlags.Members)] + internal sealed class IssueFakers : FakerContainer + { + private readonly Lazy> _lazyEngagementFaker = new Lazy>(() => + new Faker() + .UseSeed(GetFakerSeed()) + .RuleFor(engagement => engagement.Name, faker => faker.Lorem.Word())); + + private readonly Lazy> _lazyEngagementPartyFaker = new Lazy>(() => + new Faker() + .UseSeed(GetFakerSeed()) + .RuleFor(party => party.Role, faker => faker.Lorem.Word()) + .RuleFor(party => party.ShortName, faker => faker.Lorem.Word())); + + private readonly Lazy> _lazyDocumentTypeFaker = new Lazy>(() => + new Faker() + .UseSeed(GetFakerSeed()) + .RuleFor(documentType => documentType.Description, faker => faker.Lorem.Sentence())); + + public Faker Engagement => _lazyEngagementFaker.Value; + public Faker EngagementParty => _lazyEngagementPartyFaker.Value; + public Faker DocumentType => _lazyDocumentTypeFaker.Value; + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/IssueTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/IssueTests.cs new file mode 100644 index 0000000000..3d8d4573e8 --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/IssueTests.cs @@ -0,0 +1,216 @@ +using System; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using FluentAssertions; +using JsonApiDotNetCore.Resources; +using JsonApiDotNetCore.Serialization.Objects; +using JsonApiDotNetCoreExampleTests.Startups; +using Microsoft.Extensions.DependencyInjection; +using TestBuildingBlocks; +using Xunit; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.Issue988 +{ + public sealed class IssueTests : IClassFixture, IssueDbContext>> + { + private readonly ExampleIntegrationTestContext, IssueDbContext> _testContext; + private readonly IssueFakers _fakers = new IssueFakers(); + + public IssueTests(ExampleIntegrationTestContext, IssueDbContext> testContext) + { + _testContext = testContext; + + testContext.UseController(); + testContext.UseController(); + testContext.UseController(); + + testContext.ConfigureServicesAfterStartup(services => + { + services.AddScoped, EngagementPartyResourceDefinition>(); + }); + } + + [Fact] + public async Task Can_get_primary_resource_by_ID() + { + // Arrange + Engagement engagement = _fakers.Engagement.Generate(); + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + dbContext.Engagements.Add(engagement); + await dbContext.SaveChangesAsync(); + }); + + string route = "/engagements/" + engagement.StringId; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); + + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); + + responseDocument.SingleData.Should().NotBeNull(); + responseDocument.SingleData.Id.Should().Be(engagement.StringId); + + responseDocument.Included.Should().BeNull(); + } + + [Fact(Skip = "Fails in EF Core with: Invalid include path: 'FirstParties' - couldn't find navigation for: 'FirstParties'")] + public async Task Can_get_primary_resource_by_ID_with_includes() + { + // Arrange + Engagement engagement = _fakers.Engagement.Generate(); + engagement.Parties = _fakers.EngagementParty.Generate(3); + engagement.Parties.ElementAt(0).Role = ModelConstants.FirstPartyRoleName; + engagement.Parties.ElementAt(1).Role = ModelConstants.SecondPartyRoleName; + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + dbContext.Engagements.Add(engagement); + await dbContext.SaveChangesAsync(); + }); + + string route = $"/engagements/{engagement.StringId}?include=firstParties,secondParties"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); + + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); + + responseDocument.SingleData.Should().NotBeNull(); + responseDocument.SingleData.Id.Should().Be(engagement.StringId); + + responseDocument.Included.Should().HaveCount(2); + responseDocument.Included.Should().ContainSingle(resourceObject => resourceObject.Id == engagement.Parties.ElementAt(0).StringId); + responseDocument.Included.Should().ContainSingle(resourceObject => resourceObject.Id == engagement.Parties.ElementAt(1).StringId); + } + + [Fact] + public async Task Can_get_secondary_resources_by_ID_without_sort() + { + // Arrange + Engagement engagement = _fakers.Engagement.Generate(); + engagement.Parties = _fakers.EngagementParty.Generate(3); + engagement.Parties.ElementAt(0).Role = ModelConstants.SecondPartyRoleName; + engagement.Parties.ElementAt(1).Role = ModelConstants.FirstPartyRoleName; + engagement.Parties.ElementAt(1).ShortName = "B"; + engagement.Parties.ElementAt(2).Role = ModelConstants.FirstPartyRoleName; + engagement.Parties.ElementAt(2).ShortName = "A"; + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + dbContext.Engagements.Add(engagement); + await dbContext.SaveChangesAsync(); + }); + + string route = $"/engagements/{engagement.StringId}/parties"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); + + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); + + responseDocument.ManyData.Should().HaveCount(3); + responseDocument.ManyData[0].Id.Should().Be(engagement.Parties.ElementAt(2).StringId); + responseDocument.ManyData[1].Id.Should().Be(engagement.Parties.ElementAt(1).StringId); + responseDocument.ManyData[2].Id.Should().Be(engagement.Parties.ElementAt(0).StringId); + + responseDocument.Included.Should().BeNull(); + } + + [Fact] + public async Task Can_get_secondary_resources_by_ID_with_sort() + { + // Arrange + Engagement engagement = _fakers.Engagement.Generate(); + engagement.Parties = _fakers.EngagementParty.Generate(2); + engagement.Parties.ElementAt(0).ShortName = "B"; + engagement.Parties.ElementAt(1).ShortName = "A"; + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + dbContext.Engagements.Add(engagement); + await dbContext.SaveChangesAsync(); + }); + + string route = $"/engagements/{engagement.StringId}/parties?sort=shortName"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); + + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); + + responseDocument.ManyData.Should().HaveCount(2); + responseDocument.ManyData[0].Id.Should().Be(engagement.Parties.ElementAt(1).StringId); + responseDocument.ManyData[1].Id.Should().Be(engagement.Parties.ElementAt(0).StringId); + + responseDocument.Included.Should().BeNull(); + } + + [Fact] + public async Task Can_get_secondary_resources_by_ID_with_descending_sort() + { + // Arrange + Engagement engagement = _fakers.Engagement.Generate(); + engagement.Parties = _fakers.EngagementParty.Generate(2); + engagement.Parties.ElementAt(0).ShortName = "A"; + engagement.Parties.ElementAt(1).ShortName = "B"; + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + dbContext.Engagements.Add(engagement); + await dbContext.SaveChangesAsync(); + }); + + string route = $"/engagements/{engagement.StringId}/parties?sort=-shortName"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); + + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); + + responseDocument.ManyData.Should().HaveCount(2); + responseDocument.ManyData[0].Id.Should().Be(engagement.Parties.ElementAt(1).StringId); + responseDocument.ManyData[1].Id.Should().Be(engagement.Parties.ElementAt(0).StringId); + + responseDocument.Included.Should().BeNull(); + } + + [Fact(Skip = "Fails in EF Core with: Invalid include path: 'FirstParties' - couldn't find navigation for: 'FirstParties'")] + public async Task Can_get_unmapped_secondary_resources_by_ID() + { + // Arrange + Engagement engagement = _fakers.Engagement.Generate(); + engagement.Parties = _fakers.EngagementParty.Generate(3); + engagement.Parties.ElementAt(0).Role = ModelConstants.FirstPartyRoleName; + engagement.Parties.ElementAt(1).Role = ModelConstants.FirstPartyRoleName; + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + dbContext.Engagements.Add(engagement); + await dbContext.SaveChangesAsync(); + }); + + string route = $"/engagements/{engagement.StringId}/firstParties"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); + + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); + + responseDocument.ManyData.Should().HaveCount(2); + responseDocument.ManyData.Should().ContainSingle(resourceObject => resourceObject.Id == engagement.Parties.ElementAt(0).StringId); + responseDocument.ManyData.Should().ContainSingle(resourceObject => resourceObject.Id == engagement.Parties.ElementAt(1).StringId); + + responseDocument.Included.Should().BeNull(); + } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/ModelConstants.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/ModelConstants.cs new file mode 100644 index 0000000000..a9d732f635 --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/ModelConstants.cs @@ -0,0 +1,10 @@ +#pragma warning disable AV1008 // Class should not be static + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.Issue988 +{ + internal static class ModelConstants + { + public const string FirstPartyRoleName = "P1"; + public const string SecondPartyRoleName = "P2"; + } +} From 16270b6f0c11ae5ecb9b0444fd05db47f83d09ed Mon Sep 17 00:00:00 2001 From: Bart Koelman Date: Thu, 6 May 2021 12:30:39 +0200 Subject: [PATCH 2/3] Update to run on EF Core 5 --- Directory.Build.props | 4 ++-- .../Issue988/EngagementPartyResourceDefinition.cs | 2 +- .../IntegrationTests/Issue988/IssueTests.cs | 4 ++-- .../RequiredRelationships/DefaultBehaviorTests.cs | 6 +++--- test/TestBuildingBlocks/TestBuildingBlocks.csproj | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index ae0468de7d..e1d2a04da6 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -2,8 +2,8 @@ netcoreapp3.1 3.1.* - 3.1.* - 3.1.* + 5.0.* + 5.0.* $(SolutionDir)CodingGuidelines.ruleset diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/EngagementPartyResourceDefinition.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/EngagementPartyResourceDefinition.cs index c054d513f1..f9cff0f024 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/EngagementPartyResourceDefinition.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/EngagementPartyResourceDefinition.cs @@ -17,7 +17,7 @@ public EngagementPartyResourceDefinition(IResourceGraph resourceGraph) } /// - public override SortExpression OnApplySort(SortExpression? existingSort) + public override SortExpression OnApplySort(SortExpression existingSort) { if (existingSort != null) { diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/IssueTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/IssueTests.cs index 3d8d4573e8..e7265be8f9 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/IssueTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/IssueTests.cs @@ -58,7 +58,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => responseDocument.Included.Should().BeNull(); } - [Fact(Skip = "Fails in EF Core with: Invalid include path: 'FirstParties' - couldn't find navigation for: 'FirstParties'")] + [Fact(Skip = "Fails in EF Core with: Unable to find navigation 'FirstParties' specified in string based include path 'FirstParties'.")] public async Task Can_get_primary_resource_by_ID_with_includes() { // Arrange @@ -183,7 +183,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => responseDocument.Included.Should().BeNull(); } - [Fact(Skip = "Fails in EF Core with: Invalid include path: 'FirstParties' - couldn't find navigation for: 'FirstParties'")] + [Fact(Skip = "Fails in EF Core with: Unable to find navigation 'FirstParties' specified in string based include path 'FirstParties'.")] public async Task Can_get_unmapped_secondary_resources_by_ID() { // Arrange diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/RequiredRelationships/DefaultBehaviorTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/RequiredRelationships/DefaultBehaviorTests.cs index fb5e465329..085127f7b6 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/RequiredRelationships/DefaultBehaviorTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/RequiredRelationships/DefaultBehaviorTests.cs @@ -94,7 +94,7 @@ public async Task Cannot_create_dependent_side_of_required_OneToOne_relationship Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.InternalServerError); error.Title.Should().Be("An unhandled error occurred while processing this request."); - error.Detail.Should().Be("Failed to persist changes in the underlying data store."); + error.Detail.Should().StartWith("The value of 'Shipment.Id' is unknown when attempting to save changes."); } [Fact] @@ -431,7 +431,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.InternalServerError); error.Title.Should().Be("An unhandled error occurred while processing this request."); - error.Detail.Should().StartWith("The property 'Id' on entity type 'Shipment' is part of a key and so cannot be modified or marked as modified."); + error.Detail.Should().StartWith("The property 'Shipment.Id' is part of a key and so cannot be modified or marked as modified."); } [Fact] @@ -473,7 +473,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => Error error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.InternalServerError); error.Title.Should().Be("An unhandled error occurred while processing this request."); - error.Detail.Should().StartWith("The property 'Id' on entity type 'Shipment' is part of a key and so cannot be modified or marked as modified."); + error.Detail.Should().StartWith("The property 'Shipment.Id' is part of a key and so cannot be modified or marked as modified."); } } } diff --git a/test/TestBuildingBlocks/TestBuildingBlocks.csproj b/test/TestBuildingBlocks/TestBuildingBlocks.csproj index 3aa8cb646a..3af745c1f0 100644 --- a/test/TestBuildingBlocks/TestBuildingBlocks.csproj +++ b/test/TestBuildingBlocks/TestBuildingBlocks.csproj @@ -12,7 +12,7 @@ - + From 07536950625d774e9a224b19a2b2f7ef0c4c1e56 Mon Sep 17 00:00:00 2001 From: Bart Koelman Date: Thu, 6 May 2021 15:47:13 +0200 Subject: [PATCH 3/3] Removed DocumentType, suppressed Include warning, updated DbContext mapping --- .../IntegrationTests/Issue988/DocumentType.cs | 14 -------------- .../Issue988/DocumentTypesController.cs | 16 ---------------- .../IntegrationTests/Issue988/Engagement.cs | 3 --- .../IntegrationTests/Issue988/IssueDbContext.cs | 12 +++++++++++- .../IntegrationTests/Issue988/IssueFakers.cs | 8 -------- .../IntegrationTests/Issue988/IssueTests.cs | 5 ++--- .../BaseIntegrationTestContext.cs | 3 +++ 7 files changed, 16 insertions(+), 45 deletions(-) delete mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/DocumentType.cs delete mode 100644 test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/DocumentTypesController.cs diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/DocumentType.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/DocumentType.cs deleted file mode 100644 index 8f3343f5d0..0000000000 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/DocumentType.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using JetBrains.Annotations; -using JsonApiDotNetCore.Resources; -using JsonApiDotNetCore.Resources.Annotations; - -namespace JsonApiDotNetCoreExampleTests.IntegrationTests.Issue988 -{ - [UsedImplicitly(ImplicitUseTargetFlags.Members)] - public sealed class DocumentType : Identifiable - { - [Attr] - public string Description { get; set; } - } -} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/DocumentTypesController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/DocumentTypesController.cs deleted file mode 100644 index abb114b918..0000000000 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/DocumentTypesController.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.Logging; - -namespace JsonApiDotNetCoreExampleTests.IntegrationTests.Issue988 -{ - public sealed class DocumentTypesController : JsonApiController - { - public DocumentTypesController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { - } - } -} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/Engagement.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/Engagement.cs index bfb9216450..2d4c092f54 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/Engagement.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/Engagement.cs @@ -17,9 +17,6 @@ public sealed class Engagement : EntityBase // Navigation Properties - [HasMany] - public ICollection DocumentTypes { get; set; } //= new List(); - [HasMany] [EagerLoad] public ICollection Parties { get; set; } //= new List(); diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/IssueDbContext.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/IssueDbContext.cs index 8eb8490c54..e9354dafe6 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/IssueDbContext.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/IssueDbContext.cs @@ -1,6 +1,8 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +// @formatter:wrap_chained_method_calls chop_always + namespace JsonApiDotNetCoreExampleTests.IntegrationTests.Issue988 { [UsedImplicitly(ImplicitUseTargetFlags.Members)] @@ -8,11 +10,19 @@ public sealed class IssueDbContext : DbContext { public DbSet Engagements { get; set; } public DbSet EngagementParties { get; set; } - public DbSet DocumentTypes { get; set; } public IssueDbContext(DbContextOptions options) : base(options) { } + + protected override void OnModelCreating(ModelBuilder builder) + { + builder.Entity() + .HasOne(engagementParty => engagementParty.Engagement) + .WithMany(engagement => engagement.Parties) + .IsRequired() + .OnDelete(DeleteBehavior.Restrict); + } } } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/IssueFakers.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/IssueFakers.cs index c1a227bd21..2c0a9026f9 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/IssueFakers.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/IssueFakers.cs @@ -1,6 +1,5 @@ using System; using Bogus; -using JetBrains.Annotations; using TestBuildingBlocks; // @formatter:wrap_chained_method_calls chop_always @@ -8,7 +7,6 @@ namespace JsonApiDotNetCoreExampleTests.IntegrationTests.Issue988 { - [UsedImplicitly(ImplicitUseTargetFlags.Members)] internal sealed class IssueFakers : FakerContainer { private readonly Lazy> _lazyEngagementFaker = new Lazy>(() => @@ -22,13 +20,7 @@ internal sealed class IssueFakers : FakerContainer .RuleFor(party => party.Role, faker => faker.Lorem.Word()) .RuleFor(party => party.ShortName, faker => faker.Lorem.Word())); - private readonly Lazy> _lazyDocumentTypeFaker = new Lazy>(() => - new Faker() - .UseSeed(GetFakerSeed()) - .RuleFor(documentType => documentType.Description, faker => faker.Lorem.Sentence())); - public Faker Engagement => _lazyEngagementFaker.Value; public Faker EngagementParty => _lazyEngagementPartyFaker.Value; - public Faker DocumentType => _lazyDocumentTypeFaker.Value; } } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/IssueTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/IssueTests.cs index e7265be8f9..adaa450930 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/IssueTests.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Issue988/IssueTests.cs @@ -24,7 +24,6 @@ public IssueTests(ExampleIntegrationTestContext, testContext.UseController(); testContext.UseController(); - testContext.UseController(); testContext.ConfigureServicesAfterStartup(services => { @@ -58,7 +57,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => responseDocument.Included.Should().BeNull(); } - [Fact(Skip = "Fails in EF Core with: Unable to find navigation 'FirstParties' specified in string based include path 'FirstParties'.")] + [Fact] public async Task Can_get_primary_resource_by_ID_with_includes() { // Arrange @@ -183,7 +182,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => responseDocument.Included.Should().BeNull(); } - [Fact(Skip = "Fails in EF Core with: Unable to find navigation 'FirstParties' specified in string based include path 'FirstParties'.")] + [Fact] public async Task Can_get_unmapped_secondary_resources_by_ID() { // Arrange diff --git a/test/TestBuildingBlocks/BaseIntegrationTestContext.cs b/test/TestBuildingBlocks/BaseIntegrationTestContext.cs index aba34ea755..37a5757ad9 100644 --- a/test/TestBuildingBlocks/BaseIntegrationTestContext.cs +++ b/test/TestBuildingBlocks/BaseIntegrationTestContext.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; @@ -74,6 +75,8 @@ private WebApplicationFactory CreateFactory() options.UseNpgsql(dbConnectionString); options.EnableSensitiveDataLogging(); options.EnableDetailedErrors(); + + options.ConfigureWarnings(builder => builder.Ignore(CoreEventId.InvalidIncludePathError)); }); });