diff --git a/src/JsonApiDotNetCore/Queries/QueryLayerComposer.cs b/src/JsonApiDotNetCore/Queries/QueryLayerComposer.cs index 141800cdfd..f7843b12cf 100644 --- a/src/JsonApiDotNetCore/Queries/QueryLayerComposer.cs +++ b/src/JsonApiDotNetCore/Queries/QueryLayerComposer.cs @@ -96,18 +96,25 @@ public QueryLayerComposer(IEnumerable constraintProvid // @formatter:wrap_chained_method_calls restore FilterExpression? primaryFilter = GetFilter(Array.Empty(), hasManyRelationship.LeftType); - FilterExpression? secondaryFilter = GetFilter(filtersInSecondaryScope, hasManyRelationship.RightType); - FilterExpression inverseFilter = GetInverseRelationshipFilter(primaryId, hasManyRelationship, inverseRelationship); + if (primaryFilter != null && inverseRelationship is HasOneAttribute) + { + // We can't lift the field chains in a primary filter, because there's no way for a custom filter expression to express + // the scope of its chains. See https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/1671. + return null; + } + + FilterExpression? secondaryFilter = GetFilter(filtersInSecondaryScope, hasManyRelationship.RightType); + FilterExpression inverseFilter = GetInverseRelationshipFilter(primaryId, primaryFilter, hasManyRelationship, inverseRelationship); - return LogicalExpression.Compose(LogicalOperator.And, inverseFilter, primaryFilter, secondaryFilter); + return LogicalExpression.Compose(LogicalOperator.And, inverseFilter, secondaryFilter); } - private static FilterExpression GetInverseRelationshipFilter([DisallowNull] TId primaryId, HasManyAttribute relationship, - RelationshipAttribute inverseRelationship) + private static FilterExpression GetInverseRelationshipFilter([DisallowNull] TId primaryId, FilterExpression? primaryFilter, + HasManyAttribute relationship, RelationshipAttribute inverseRelationship) { return inverseRelationship is HasManyAttribute hasManyInverseRelationship - ? GetInverseHasManyRelationshipFilter(primaryId, relationship, hasManyInverseRelationship) + ? GetInverseHasManyRelationshipFilter(primaryId, primaryFilter, relationship, hasManyInverseRelationship) : GetInverseHasOneRelationshipFilter(primaryId, relationship, (HasOneAttribute)inverseRelationship); } @@ -120,14 +127,15 @@ private static ComparisonExpression GetInverseHasOneRelationshipFilter([Dis return new ComparisonExpression(ComparisonOperator.Equals, idChain, new LiteralConstantExpression(primaryId)); } - private static HasExpression GetInverseHasManyRelationshipFilter([DisallowNull] TId primaryId, HasManyAttribute relationship, - HasManyAttribute inverseRelationship) + private static HasExpression GetInverseHasManyRelationshipFilter([DisallowNull] TId primaryId, FilterExpression? primaryFilter, + HasManyAttribute relationship, HasManyAttribute inverseRelationship) { AttrAttribute idAttribute = GetIdAttribute(relationship.LeftType); var idChain = new ResourceFieldChainExpression(ImmutableArray.Create(idAttribute)); var idComparison = new ComparisonExpression(ComparisonOperator.Equals, idChain, new LiteralConstantExpression(primaryId)); - return new HasExpression(new ResourceFieldChainExpression(inverseRelationship), idComparison); + FilterExpression filter = LogicalExpression.Compose(LogicalOperator.And, idComparison, primaryFilter)!; + return new HasExpression(new ResourceFieldChainExpression(inverseRelationship), filter); } /// diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/Constellation.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/Constellation.cs new file mode 100644 index 0000000000..371c72bcf8 --- /dev/null +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/Constellation.cs @@ -0,0 +1,21 @@ +using System.ComponentModel.DataAnnotations; +using JetBrains.Annotations; +using JsonApiDotNetCore.Resources; +using JsonApiDotNetCore.Resources.Annotations; + +namespace JsonApiDotNetCoreTests.IntegrationTests.ResourceDefinitions.Reading; + +[UsedImplicitly(ImplicitUseTargetFlags.Members)] +[Resource(ControllerNamespace = "JsonApiDotNetCoreTests.IntegrationTests.ResourceDefinitions.Reading")] +public sealed class Constellation : Identifiable +{ + [Attr] + public string Name { get; set; } = null!; + + [Attr] + [Required] + public Season? VisibleDuring { get; set; } + + [HasMany] + public ISet Stars { get; set; } = new HashSet(); +} diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/ConstellationDefinition.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/ConstellationDefinition.cs new file mode 100644 index 0000000000..b74d3c4947 --- /dev/null +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/ConstellationDefinition.cs @@ -0,0 +1,33 @@ +using JetBrains.Annotations; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Queries.Expressions; +using JsonApiDotNetCore.Resources.Annotations; + +namespace JsonApiDotNetCoreTests.IntegrationTests.ResourceDefinitions.Reading; + +[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] +public sealed class ConstellationDefinition( + IResourceGraph resourceGraph, IClientSettingsProvider clientSettingsProvider, ResourceDefinitionHitCounter hitCounter) + : HitCountingResourceDefinition(resourceGraph, hitCounter) +{ + private readonly IClientSettingsProvider _clientSettingsProvider = clientSettingsProvider; + + protected override ResourceDefinitionExtensibilityPoints ExtensibilityPointsToTrack => ResourceDefinitionExtensibilityPoints.Reading; + + public override FilterExpression? OnApplyFilter(FilterExpression? existingFilter) + { + FilterExpression? baseFilter = base.OnApplyFilter(existingFilter); + + if (_clientSettingsProvider.AreConstellationsVisibleDuringWinterHidden) + { + AttrAttribute visibleDuringAttribute = ResourceType.GetAttributeByPropertyName(nameof(Constellation.VisibleDuring)); + var visibleDuringChain = new ResourceFieldChainExpression(visibleDuringAttribute); + var visibleDuringComparison = new ComparisonExpression(ComparisonOperator.Equals, visibleDuringChain, new LiteralConstantExpression(Season.Winter)); + var notVisibleDuringComparison = new NotExpression(visibleDuringComparison); + + return LogicalExpression.Compose(LogicalOperator.And, baseFilter, notVisibleDuringComparison); + } + + return baseFilter; + } +} diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/IClientSettingsProvider.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/IClientSettingsProvider.cs index f67cd3d993..2200921901 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/IClientSettingsProvider.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/IClientSettingsProvider.cs @@ -2,6 +2,8 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.ResourceDefinitions.Reading; public interface IClientSettingsProvider { + bool AreVeryLargeStarsHidden { get; } + bool AreConstellationsVisibleDuringWinterHidden { get; } bool IsIncludePlanetMoonsBlocked { get; } bool ArePlanetsWithPrivateNameHidden { get; } bool IsStarGivingLightToMoonAutoIncluded { get; } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/ResourceDefinitionReadTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/ResourceDefinitionReadTests.cs index 64a298555f..c22619a825 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/ResourceDefinitionReadTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/ResourceDefinitionReadTests.cs @@ -17,12 +17,14 @@ public ResourceDefinitionReadTests(IntegrationTestContext(); testContext.UseController(); testContext.UseController(); testContext.UseController(); testContext.ConfigureServices(services => { + services.AddResourceDefinition(); services.AddResourceDefinition(); services.AddResourceDefinition(); services.AddResourceDefinition(); @@ -323,7 +325,6 @@ public async Task Filter_from_resource_definition_is_applied_at_secondary_endpoi await _testContext.RunOnDatabaseAsync(async dbContext => { - await dbContext.ClearTableAsync(); dbContext.Stars.Add(star); await dbContext.SaveChangesAsync(); }); @@ -375,7 +376,6 @@ public async Task Filter_from_resource_definition_is_applied_at_relationship_end await _testContext.RunOnDatabaseAsync(async dbContext => { - await dbContext.ClearTableAsync(); dbContext.Stars.Add(star); await dbContext.SaveChangesAsync(); }); @@ -409,6 +409,198 @@ await _testContext.RunOnDatabaseAsync(async dbContext => }, options => options.WithStrictOrdering()); } + [Fact] + public async Task No_total_when_resource_definition_has_filter_on_inverse_ManyToOne_at_secondary_endpoint() + { + // Arrange + var hitCounter = _testContext.Factory.Services.GetRequiredService(); + + var settingsProvider = (TestClientSettingsProvider)_testContext.Factory.Services.GetRequiredService(); + settingsProvider.HideVeryLargeStars(); + + Star star = _fakers.Star.GenerateOne(); + star.Planets = _fakers.Planet.GenerateSet(1); + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + dbContext.Stars.Add(star); + await dbContext.SaveChangesAsync(); + }); + + string route = $"/stars/{star.StringId}/planets"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); + + responseDocument.Data.ManyValue.Should().HaveCount(1); + responseDocument.Data.ManyValue[0].Id.Should().Be(star.Planets.ElementAt(0).StringId); + + responseDocument.Meta.Should().BeNull(); + + hitCounter.HitExtensibilityPoints.Should().BeEquivalentTo(new[] + { + (typeof(Star), ResourceDefinitionExtensibilityPoints.OnApplyFilter), + (typeof(Planet), ResourceDefinitionExtensibilityPoints.OnApplyPagination), + (typeof(Planet), ResourceDefinitionExtensibilityPoints.OnApplyFilter), + (typeof(Planet), ResourceDefinitionExtensibilityPoints.OnApplySort), + (typeof(Planet), ResourceDefinitionExtensibilityPoints.OnApplySparseFieldSet), + (typeof(Planet), ResourceDefinitionExtensibilityPoints.OnApplyIncludes), + (typeof(Star), ResourceDefinitionExtensibilityPoints.OnApplySparseFieldSet), + (typeof(Star), ResourceDefinitionExtensibilityPoints.OnApplyFilter), + (typeof(Planet), ResourceDefinitionExtensibilityPoints.OnApplySparseFieldSet), + (typeof(Planet), ResourceDefinitionExtensibilityPoints.GetMeta) + }, options => options.WithStrictOrdering()); + } + + [Fact] + public async Task Has_total_when_resource_definition_has_filter_on_inverse_ManyToMany_at_secondary_endpoint() + { + // Arrange + var hitCounter = _testContext.Factory.Services.GetRequiredService(); + + var settingsProvider = (TestClientSettingsProvider)_testContext.Factory.Services.GetRequiredService(); + settingsProvider.HideConstellationsVisibleDuringWinter(); + + Constellation constellation = _fakers.Constellation.GenerateOne(); + constellation.VisibleDuring = Season.Winter; + constellation.Stars = _fakers.Star.GenerateSet(1); + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + dbContext.Constellations.Add(constellation); + await dbContext.SaveChangesAsync(); + }); + + string route = $"/constellations/{constellation.StringId}/stars"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.NotFound); + + responseDocument.Errors.Should().HaveCount(1); + + ErrorObject error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.NotFound); + error.Title.Should().Be("The requested resource does not exist."); + error.Detail.Should().Be($"Resource of type 'constellations' with ID '{constellation.StringId}' does not exist."); + + responseDocument.Meta.Should().ContainTotal(0); + + hitCounter.HitExtensibilityPoints.Should().BeEquivalentTo(new[] + { + (typeof(Constellation), ResourceDefinitionExtensibilityPoints.OnApplyFilter), + (typeof(Star), ResourceDefinitionExtensibilityPoints.OnApplyFilter), + (typeof(Star), ResourceDefinitionExtensibilityPoints.OnApplyPagination), + (typeof(Star), ResourceDefinitionExtensibilityPoints.OnApplyFilter), + (typeof(Star), ResourceDefinitionExtensibilityPoints.OnApplySort), + (typeof(Star), ResourceDefinitionExtensibilityPoints.OnApplySparseFieldSet), + (typeof(Star), ResourceDefinitionExtensibilityPoints.OnApplyIncludes), + (typeof(Constellation), ResourceDefinitionExtensibilityPoints.OnApplySparseFieldSet), + (typeof(Constellation), ResourceDefinitionExtensibilityPoints.OnApplyFilter) + }, options => options.WithStrictOrdering()); + } + + [Fact] + public async Task No_total_when_resource_definition_has_filter_on_inverse_ManyToOne_at_relationship_endpoint() + { + // Arrange + var hitCounter = _testContext.Factory.Services.GetRequiredService(); + + var settingsProvider = (TestClientSettingsProvider)_testContext.Factory.Services.GetRequiredService(); + settingsProvider.HideVeryLargeStars(); + + Star star = _fakers.Star.GenerateOne(); + star.Planets = _fakers.Planet.GenerateSet(1); + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + dbContext.Stars.Add(star); + await dbContext.SaveChangesAsync(); + }); + + string route = $"/stars/{star.StringId}/relationships/planets"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); + + responseDocument.Data.ManyValue.Should().HaveCount(1); + responseDocument.Data.ManyValue[0].Id.Should().Be(star.Planets.ElementAt(0).StringId); + + responseDocument.Meta.Should().BeNull(); + + hitCounter.HitExtensibilityPoints.Should().BeEquivalentTo(new[] + { + (typeof(Star), ResourceDefinitionExtensibilityPoints.OnApplyFilter), + (typeof(Planet), ResourceDefinitionExtensibilityPoints.OnApplyPagination), + (typeof(Planet), ResourceDefinitionExtensibilityPoints.OnApplyFilter), + (typeof(Planet), ResourceDefinitionExtensibilityPoints.OnApplySort), + (typeof(Planet), ResourceDefinitionExtensibilityPoints.OnApplySparseFieldSet), + (typeof(Planet), ResourceDefinitionExtensibilityPoints.OnApplyIncludes), + (typeof(Planet), ResourceDefinitionExtensibilityPoints.OnApplySparseFieldSet), + (typeof(Star), ResourceDefinitionExtensibilityPoints.OnApplySparseFieldSet), + (typeof(Star), ResourceDefinitionExtensibilityPoints.OnApplyFilter) + }, options => options.WithStrictOrdering()); + } + + [Fact] + public async Task Has_total_when_resource_definition_has_filter_on_inverse_ManyToMany_at_relationship_endpoint() + { + // Arrange + var hitCounter = _testContext.Factory.Services.GetRequiredService(); + + var settingsProvider = (TestClientSettingsProvider)_testContext.Factory.Services.GetRequiredService(); + settingsProvider.HideConstellationsVisibleDuringWinter(); + + Constellation constellation = _fakers.Constellation.GenerateOne(); + constellation.VisibleDuring = Season.Winter; + constellation.Stars = _fakers.Star.GenerateSet(1); + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + dbContext.Constellations.Add(constellation); + await dbContext.SaveChangesAsync(); + }); + + string route = $"/constellations/{constellation.StringId}/relationships/stars"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.NotFound); + + responseDocument.Errors.Should().HaveCount(1); + + ErrorObject error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.NotFound); + error.Title.Should().Be("The requested resource does not exist."); + error.Detail.Should().Be($"Resource of type 'constellations' with ID '{constellation.StringId}' does not exist."); + + responseDocument.Meta.Should().ContainTotal(0); + + hitCounter.HitExtensibilityPoints.Should().BeEquivalentTo(new[] + { + (typeof(Constellation), ResourceDefinitionExtensibilityPoints.OnApplyFilter), + (typeof(Star), ResourceDefinitionExtensibilityPoints.OnApplyFilter), + (typeof(Star), ResourceDefinitionExtensibilityPoints.OnApplyPagination), + (typeof(Star), ResourceDefinitionExtensibilityPoints.OnApplyFilter), + (typeof(Star), ResourceDefinitionExtensibilityPoints.OnApplySort), + (typeof(Star), ResourceDefinitionExtensibilityPoints.OnApplySparseFieldSet), + (typeof(Star), ResourceDefinitionExtensibilityPoints.OnApplyIncludes), + (typeof(Star), ResourceDefinitionExtensibilityPoints.OnApplySparseFieldSet), + (typeof(Constellation), ResourceDefinitionExtensibilityPoints.OnApplySparseFieldSet), + (typeof(Constellation), ResourceDefinitionExtensibilityPoints.OnApplyFilter) + }, options => options.WithStrictOrdering()); + } + [Fact] public async Task Sort_from_resource_definition_is_applied() { diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/Season.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/Season.cs new file mode 100644 index 0000000000..16e3c0d9e4 --- /dev/null +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/Season.cs @@ -0,0 +1,12 @@ +using JetBrains.Annotations; + +namespace JsonApiDotNetCoreTests.IntegrationTests.ResourceDefinitions.Reading; + +[UsedImplicitly(ImplicitUseTargetFlags.Members)] +public enum Season +{ + Winter, + Spring, + Summer, + Fall +} diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/Star.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/Star.cs index e79c2ae8af..b8c78109ed 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/Star.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/Star.cs @@ -25,4 +25,7 @@ public sealed class Star : Identifiable [HasMany] public ISet Planets { get; set; } = new HashSet(); + + [HasMany] + public ISet IsPartOf { get; set; } = new HashSet(); } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/StarDefinition.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/StarDefinition.cs index 944cb4ca0e..3e4f9b678c 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/StarDefinition.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/StarDefinition.cs @@ -2,16 +2,35 @@ using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Queries.Expressions; +using JsonApiDotNetCore.Resources.Annotations; namespace JsonApiDotNetCoreTests.IntegrationTests.ResourceDefinitions.Reading; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] // The constructor parameters will be resolved from the container, which means you can take on any dependency that is also defined in the container. -public sealed class StarDefinition(IResourceGraph resourceGraph, ResourceDefinitionHitCounter hitCounter) +public sealed class StarDefinition(IResourceGraph resourceGraph, IClientSettingsProvider clientSettingsProvider, ResourceDefinitionHitCounter hitCounter) : HitCountingResourceDefinition(resourceGraph, hitCounter) { + private readonly IClientSettingsProvider _clientSettingsProvider = clientSettingsProvider; + protected override ResourceDefinitionExtensibilityPoints ExtensibilityPointsToTrack => ResourceDefinitionExtensibilityPoints.Reading; + public override FilterExpression? OnApplyFilter(FilterExpression? existingFilter) + { + FilterExpression? baseFilter = base.OnApplyFilter(existingFilter); + + if (_clientSettingsProvider.AreVeryLargeStarsHidden) + { + AttrAttribute solarRadiusAttribute = ResourceType.GetAttributeByPropertyName(nameof(Star.SolarRadius)); + var solarRadiusChain = new ResourceFieldChainExpression(solarRadiusAttribute); + var solarRadiusComparison = new ComparisonExpression(ComparisonOperator.LessThan, solarRadiusChain, new LiteralConstantExpression(2000M)); + + return LogicalExpression.Compose(LogicalOperator.And, baseFilter, solarRadiusComparison); + } + + return baseFilter; + } + public override SortExpression OnApplySort(SortExpression? existingSort) { base.OnApplySort(existingSort); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/TestClientSettingsProvider.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/TestClientSettingsProvider.cs index 0efc7a415e..65fa84a415 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/TestClientSettingsProvider.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/TestClientSettingsProvider.cs @@ -2,17 +2,31 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.ResourceDefinitions.Reading; internal sealed class TestClientSettingsProvider : IClientSettingsProvider { + public bool AreVeryLargeStarsHidden { get; private set; } + public bool AreConstellationsVisibleDuringWinterHidden { get; private set; } public bool IsIncludePlanetMoonsBlocked { get; private set; } public bool ArePlanetsWithPrivateNameHidden { get; private set; } public bool IsStarGivingLightToMoonAutoIncluded { get; private set; } public void ResetToDefaults() { + AreVeryLargeStarsHidden = false; + AreConstellationsVisibleDuringWinterHidden = false; IsIncludePlanetMoonsBlocked = false; ArePlanetsWithPrivateNameHidden = false; IsStarGivingLightToMoonAutoIncluded = false; } + public void HideVeryLargeStars() + { + AreVeryLargeStarsHidden = true; + } + + public void HideConstellationsVisibleDuringWinter() + { + AreConstellationsVisibleDuringWinterHidden = true; + } + public void BlockIncludePlanetMoons() { IsIncludePlanetMoonsBlocked = true; diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/UniverseDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/UniverseDbContext.cs index 94e1b73ec0..6a157ed8b2 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/UniverseDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/UniverseDbContext.cs @@ -8,6 +8,7 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.ResourceDefinitions.Reading; public sealed class UniverseDbContext(DbContextOptions options) : TestableDbContext(options) { + public DbSet Constellations => Set(); public DbSet Stars => Set(); public DbSet Planets => Set(); public DbSet Moons => Set(); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/UniverseFakers.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/UniverseFakers.cs index 4c0723e1df..86ca6a70cf 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/UniverseFakers.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/UniverseFakers.cs @@ -8,6 +8,11 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.ResourceDefinitions.Reading; internal sealed class UniverseFakers { + private readonly Lazy> _lazyConstellationFaker = new(() => new Faker() + .MakeDeterministic() + .RuleFor(constellation => constellation.Name, faker => faker.Random.Word()) + .RuleFor(constellation => constellation.VisibleDuring, faker => faker.PickRandom())); + private readonly Lazy> _lazyStarFaker = new(() => new Faker() .MakeDeterministic() .RuleFor(star => star.Name, faker => faker.Random.Word()) @@ -27,6 +32,7 @@ internal sealed class UniverseFakers .RuleFor(moon => moon.Name, faker => faker.Random.Word()) .RuleFor(moon => moon.SolarRadius, faker => faker.Random.Decimal(.01M, 1000M))); + public Faker Constellation => _lazyConstellationFaker.Value; public Faker Star => _lazyStarFaker.Value; public Faker Planet => _lazyPlanetFaker.Value; public Faker Moon => _lazyMoonFaker.Value;