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; pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy