diff --git a/src/Examples/JsonApiDotNetCoreExample/Definitions/PassportHooksDefinition.cs b/src/Examples/JsonApiDotNetCoreExample/Definitions/PassportHooksDefinition.cs index ec5cd3ed8f..5ccfcf2447 100644 --- a/src/Examples/JsonApiDotNetCoreExample/Definitions/PassportHooksDefinition.cs +++ b/src/Examples/JsonApiDotNetCoreExample/Definitions/PassportHooksDefinition.cs @@ -4,7 +4,6 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Errors; using JsonApiDotNetCore.Hooks.Internal.Execution; -using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Serialization.Objects; using JsonApiDotNetCoreExample.Models; diff --git a/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs b/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs index 96717fe796..300c4620b4 100644 --- a/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs +++ b/src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs @@ -56,41 +56,28 @@ public interface IJsonApiOptions bool UseRelativeLinks { get; } /// - /// Configures globally which links to show in the - /// object for a requested resource. Setting can be overridden per resource by - /// adding a to the class definition of that resource. + /// Configures which links to show in the + /// object. Defaults to . + /// This setting can be overruled per resource type by + /// adding on the class definition of a resource. /// LinkTypes TopLevelLinks { get; } /// - /// Configures globally which links to show in the - /// object for a requested resource. Setting can be overridden per resource by - /// adding a to the class definition of that resource. + /// Configures which links to show in the + /// object. Defaults to . + /// This setting can be overruled per resource type by + /// adding on the class definition of a resource. /// LinkTypes ResourceLinks { get; } /// - /// Configures globally which links to show in the - /// object for a requested resource. Setting can be overridden per resource by - /// adding a to the class definition of that resource. - /// This option can also be specified per relationship by using the associated links argument - /// in the constructor of . + /// Configures which links to show in the + /// object. Defaults to . + /// This setting can be overruled for all relationships per resource type by + /// adding on the class definition of a resource. + /// This can be further overruled per relationship by setting . /// - /// - /// - /// options.RelationshipLinks = LinkTypes.None; - /// - /// - /// { - /// "type": "articles", - /// "id": "4309", - /// "relationships": { - /// "author": { "data": { "type": "people", "id": "1234" } - /// } - /// } - /// } - /// - /// LinkTypes RelationshipLinks { get; } /// @@ -154,13 +141,13 @@ public interface IJsonApiOptions bool EnableLegacyFilterNotation { get; } /// - /// Determines whether the serialization setting can be overridden by using a query string parameter. + /// Determines whether the serialization setting can be controlled using a query string parameter. /// False by default. /// bool AllowQueryStringOverrideForSerializerNullValueHandling { get; } /// - /// Determines whether the serialization setting can be overridden by using a query string parameter. + /// Determines whether the serialization setting can be controlled using a query string parameter. /// False by default. /// bool AllowQueryStringOverrideForSerializerDefaultValueHandling { get; } diff --git a/src/JsonApiDotNetCore/Configuration/ResourceContext.cs b/src/JsonApiDotNetCore/Configuration/ResourceContext.cs index 632a611dd3..edde0bcc41 100644 --- a/src/JsonApiDotNetCore/Configuration/ResourceContext.cs +++ b/src/JsonApiDotNetCore/Configuration/ResourceContext.cs @@ -52,27 +52,33 @@ public class ResourceContext /// /// Configures which links to show in the - /// object for this resource. If set to , - /// the configuration will be read from . - /// Defaults to . + /// object for this resource type. + /// Defaults to , which falls back to . /// + /// + /// In the process of building the resource graph, this value is set based on usage. + /// public LinkTypes TopLevelLinks { get; internal set; } = LinkTypes.NotConfigured; /// /// Configures which links to show in the - /// object for this resource. If set to , - /// the configuration will be read from . - /// Defaults to . + /// object for this resource type. + /// Defaults to , which falls back to . /// + /// + /// In the process of building the resource graph, this value is set based on usage. + /// public LinkTypes ResourceLinks { get; internal set; } = LinkTypes.NotConfigured; /// /// Configures which links to show in the - /// for all relationships of the resource for which this attribute was instantiated. - /// If set to , the configuration will - /// be read from or - /// . Defaults to . + /// object for all relationships of this resource type. + /// Defaults to , which falls back to . + /// This can be overruled per relationship by setting . /// + /// + /// In the process of building the resource graph, this value is set based on usage. + /// public LinkTypes RelationshipLinks { get; internal set; } = LinkTypes.NotConfigured; public override string ToString() diff --git a/src/JsonApiDotNetCore/Resources/Annotations/HasManyAttribute.cs b/src/JsonApiDotNetCore/Resources/Annotations/HasManyAttribute.cs index c31aa61989..869e322ee8 100644 --- a/src/JsonApiDotNetCore/Resources/Annotations/HasManyAttribute.cs +++ b/src/JsonApiDotNetCore/Resources/Annotations/HasManyAttribute.cs @@ -5,24 +5,17 @@ namespace JsonApiDotNetCore.Resources.Annotations /// /// Used to expose a property on a resource class as a JSON:API to-many relationship (https://jsonapi.org/format/#document-resource-object-relationships). /// + /// + /// Articles { get; set; } + /// } + /// ]]> + /// [AttributeUsage(AttributeTargets.Property)] public class HasManyAttribute : RelationshipAttribute { - /// - /// Creates a HasMany relational link to another resource. - /// - /// - /// Articles { get; set; } - /// } - /// ]]> - /// - public HasManyAttribute() - { - Links = LinkTypes.All; - } } } diff --git a/src/JsonApiDotNetCore/Resources/Annotations/HasOneAttribute.cs b/src/JsonApiDotNetCore/Resources/Annotations/HasOneAttribute.cs index d958a6a4ee..020bd2b5b2 100644 --- a/src/JsonApiDotNetCore/Resources/Annotations/HasOneAttribute.cs +++ b/src/JsonApiDotNetCore/Resources/Annotations/HasOneAttribute.cs @@ -8,9 +8,5 @@ namespace JsonApiDotNetCore.Resources.Annotations [AttributeUsage(AttributeTargets.Property)] public sealed class HasOneAttribute : RelationshipAttribute { - public HasOneAttribute() - { - Links = LinkTypes.NotConfigured; - } } } diff --git a/src/JsonApiDotNetCore/Resources/Annotations/RelationshipAttribute.cs b/src/JsonApiDotNetCore/Resources/Annotations/RelationshipAttribute.cs index 21c71ae68a..4efb72832f 100644 --- a/src/JsonApiDotNetCore/Resources/Annotations/RelationshipAttribute.cs +++ b/src/JsonApiDotNetCore/Resources/Annotations/RelationshipAttribute.cs @@ -1,7 +1,6 @@ using System; using System.Reflection; using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Errors; namespace JsonApiDotNetCore.Resources.Annotations { @@ -10,8 +9,6 @@ namespace JsonApiDotNetCore.Resources.Annotations /// public abstract class RelationshipAttribute : ResourceFieldAttribute { - private LinkTypes _links; - /// /// The property name of the EF Core inverse navigation, which may or may not exist. /// Even if it exists, it may not be exposed as a JSON:API relationship. @@ -58,27 +55,12 @@ public abstract class RelationshipAttribute : ResourceFieldAttribute public Type LeftType { get; internal set; } /// - /// Configures which links to show in the object for this relationship. - /// When not explicitly assigned, the default value depends on the relationship type (see remarks). + /// Configures which links to show in the + /// object for this relationship. + /// Defaults to , which falls back to + /// and then falls back to . /// - /// - /// This defaults to for and relationships. - /// This defaults to for relationships, which means that - /// the configuration in or is used. - /// - public LinkTypes Links - { - get => _links; - set - { - if (value == LinkTypes.Paging) - { - throw new InvalidConfigurationException($"{LinkTypes.Paging:g} not allowed for argument {nameof(value)}"); - } - - _links = value; - } - } + public LinkTypes Links { get; set; } = LinkTypes.NotConfigured; /// /// Whether or not this relationship can be included using the ?include=publicName query string parameter. diff --git a/src/JsonApiDotNetCore/Resources/Annotations/ResourceLinksAttribute.cs b/src/JsonApiDotNetCore/Resources/Annotations/ResourceLinksAttribute.cs index 03927c05c9..4a4e73027e 100644 --- a/src/JsonApiDotNetCore/Resources/Annotations/ResourceLinksAttribute.cs +++ b/src/JsonApiDotNetCore/Resources/Annotations/ResourceLinksAttribute.cs @@ -1,75 +1,34 @@ using System; -using JsonApiDotNetCore.Errors; +using JsonApiDotNetCore.Configuration; namespace JsonApiDotNetCore.Resources.Annotations { - // TODO: There are no tests for this. - /// /// When put on a resource class, overrides global configuration for which links to render. /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface)] public sealed class ResourceLinksAttribute : Attribute { - private LinkTypes _topLevelLinks = LinkTypes.NotConfigured; - private LinkTypes _resourceLinks = LinkTypes.NotConfigured; - private LinkTypes _relationshipLinks = LinkTypes.NotConfigured; - /// /// Configures which links to show in the - /// section for this resource. - /// Defaults to . + /// object for this resource type. + /// Defaults to , which falls back to . /// - public LinkTypes TopLevelLinks - { - get => _topLevelLinks; - set - { - if (value == LinkTypes.Related) - { - throw new InvalidConfigurationException($"{LinkTypes.Related:g} not allowed for argument {nameof(value)}"); - } - - _topLevelLinks = value; - } - } + public LinkTypes TopLevelLinks { get; set; } = LinkTypes.NotConfigured; /// /// Configures which links to show in the - /// section for this resource. - /// Defaults to . + /// object for this resource type. + /// Defaults to , which falls back to . /// - public LinkTypes ResourceLinks - { - get => _resourceLinks; - set - { - if (value == LinkTypes.Paging) - { - throw new InvalidConfigurationException($"{LinkTypes.Paging:g} not allowed for argument {nameof(value)}"); - } - - _resourceLinks = value; - } - } - + public LinkTypes ResourceLinks { get; set; } = LinkTypes.NotConfigured; + /// /// Configures which links to show in the - /// for all relationships of the resource type on which this attribute was used. - /// Defaults to . + /// object for all relationships of this resource type. + /// Defaults to , which falls back to . + /// This can be overruled per relationship by setting . /// - public LinkTypes RelationshipLinks - { - get => _relationshipLinks; - set - { - if (value == LinkTypes.Paging) - { - throw new InvalidConfigurationException($"{LinkTypes.Paging:g} not allowed for argument {nameof(value)}"); - } - - _relationshipLinks = value; - } - } + public LinkTypes RelationshipLinks { get; set; } = LinkTypes.NotConfigured; } } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/LinkInclusionTests.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/LinkInclusionTests.cs new file mode 100644 index 0000000000..760a1ebb8c --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/LinkInclusionTests.cs @@ -0,0 +1,63 @@ +using System.Net; +using System.Threading.Tasks; +using FluentAssertions; +using JsonApiDotNetCore.Serialization.Objects; +using JsonApiDotNetCoreExampleTests.Startups; +using TestBuildingBlocks; +using Xunit; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.Links +{ + public sealed class LinkInclusionTests + : IClassFixture, LinksDbContext>> + { + private readonly ExampleIntegrationTestContext, LinksDbContext> _testContext; + private readonly LinksFakers _fakers = new LinksFakers(); + + public LinkInclusionTests(ExampleIntegrationTestContext, LinksDbContext> testContext) + { + _testContext = testContext; + } + + [Fact] + public async Task Get_primary_resource_with_include_applies_links_visibility_from_ResourceLinksAttribute() + { + // Arrange + var location = _fakers.PhotoLocation.Generate(); + location.Photo = _fakers.Photo.Generate(); + location.Album = _fakers.PhotoAlbum.Generate(); + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + dbContext.PhotoLocations.Add(location); + await dbContext.SaveChangesAsync(); + }); + + var route = $"/photoLocations/{location.StringId}?include=photo,album"; + + // Act + var (httpResponse, responseDocument) = await _testContext.ExecuteGetAsync(route); + + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); + + responseDocument.Links.Should().BeNull(); + + responseDocument.SingleData.Should().NotBeNull(); + responseDocument.SingleData.Links.Should().BeNull(); + responseDocument.SingleData.Relationships["photo"].Links.Self.Should().BeNull(); + responseDocument.SingleData.Relationships["photo"].Links.Related.Should().NotBeNull(); + responseDocument.SingleData.Relationships["album"].Links.Should().BeNull(); + + responseDocument.Included.Should().HaveCount(2); + + responseDocument.Included[0].Links.Self.Should().NotBeNull(); + responseDocument.Included[0].Relationships["location"].Links.Self.Should().NotBeNull(); + responseDocument.Included[0].Relationships["location"].Links.Related.Should().NotBeNull(); + + responseDocument.Included[1].Links.Self.Should().NotBeNull(); + responseDocument.Included[1].Relationships["photos"].Links.Self.Should().NotBeNull(); + responseDocument.Included[1].Relationships["photos"].Links.Related.Should().NotBeNull(); + } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/LinksDbContext.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/LinksDbContext.cs index bb34911642..8f1e638891 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/LinksDbContext.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/LinksDbContext.cs @@ -6,10 +6,19 @@ public sealed class LinksDbContext : DbContext { public DbSet PhotoAlbums { get; set; } public DbSet Photos { get; set; } + public DbSet PhotoLocations { get; set; } public LinksDbContext(DbContextOptions options) : base(options) { } + + protected override void OnModelCreating(ModelBuilder builder) + { + builder.Entity() + .HasOne(photo => photo.Location) + .WithOne(location => location.Photo) + .HasForeignKey("PhotoLocationKey"); + } } } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/LinksFakers.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/LinksFakers.cs index 6e751dd00b..2bd9552fa4 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/LinksFakers.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/LinksFakers.cs @@ -16,7 +16,15 @@ internal sealed class LinksFakers : FakerContainer .UseSeed(GetFakerSeed()) .RuleFor(photo => photo.Url, f => f.Image.PlaceImgUrl())); + private readonly Lazy> _lazyPhotoLocationFaker = new Lazy>(() => + new Faker() + .UseSeed(GetFakerSeed()) + .RuleFor(photoLocation => photoLocation.PlaceName, f => f.Address.FullAddress()) + .RuleFor(photoLocation => photoLocation.Latitude, f => f.Address.Latitude()) + .RuleFor(photoLocation => photoLocation.Longitude, f => f.Address.Longitude())); + public Faker PhotoAlbum => _lazyPhotoAlbumFaker.Value; public Faker Photo => _lazyPhotoFaker.Value; + public Faker PhotoLocation => _lazyPhotoLocationFaker.Value; } } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/Photo.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/Photo.cs index 8af6915e1d..592d5ccb12 100644 --- a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/Photo.cs +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/Photo.cs @@ -9,6 +9,9 @@ public sealed class Photo : Identifiable [Attr] public string Url { get; set; } + [HasOne] + public PhotoLocation Location { get; set; } + [HasOne] public PhotoAlbum Album { get; set; } } diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/PhotoLocation.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/PhotoLocation.cs new file mode 100644 index 0000000000..362c6edb36 --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/PhotoLocation.cs @@ -0,0 +1,24 @@ +using JsonApiDotNetCore.Resources; +using JsonApiDotNetCore.Resources.Annotations; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.Links +{ + [ResourceLinks(TopLevelLinks = LinkTypes.None, ResourceLinks = LinkTypes.None, RelationshipLinks = LinkTypes.Related)] + public sealed class PhotoLocation : Identifiable + { + [Attr] + public string PlaceName { get; set; } + + [Attr] + public double Latitude { get; set; } + + [Attr] + public double Longitude { get; set; } + + [HasOne] + public Photo Photo { get; set; } + + [HasOne(Links = LinkTypes.None)] + public PhotoAlbum Album { get; set; } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/PhotoLocationsController.cs b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/PhotoLocationsController.cs new file mode 100644 index 0000000000..1398ee84b1 --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/IntegrationTests/Links/PhotoLocationsController.cs @@ -0,0 +1,16 @@ +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Controllers; +using JsonApiDotNetCore.Services; +using Microsoft.Extensions.Logging; + +namespace JsonApiDotNetCoreExampleTests.IntegrationTests.Links +{ + public sealed class PhotoLocationsController : JsonApiController + { + public PhotoLocationsController(IJsonApiOptions options, ILoggerFactory loggerFactory, + IResourceService resourceService) + : base(options, loggerFactory, resourceService) + { + } + } +} diff --git a/test/JsonApiDotNetCoreExampleTests/UnitTests/Links/LinkInclusionTests.cs b/test/JsonApiDotNetCoreExampleTests/UnitTests/Links/LinkInclusionTests.cs new file mode 100644 index 0000000000..1134e7782b --- /dev/null +++ b/test/JsonApiDotNetCoreExampleTests/UnitTests/Links/LinkInclusionTests.cs @@ -0,0 +1,395 @@ +using FluentAssertions; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Middleware; +using JsonApiDotNetCore.Queries; +using JsonApiDotNetCore.QueryStrings; +using JsonApiDotNetCore.Resources; +using JsonApiDotNetCore.Resources.Annotations; +using JsonApiDotNetCore.Serialization.Building; +using Microsoft.AspNetCore.Http; +using Xunit; + +namespace JsonApiDotNetCoreExampleTests.UnitTests.Links +{ + public sealed class LinkInclusionTests + { + [Theory] + [InlineData(LinkTypes.NotConfigured, LinkTypes.NotConfigured, LinkTypes.None)] + [InlineData(LinkTypes.NotConfigured, LinkTypes.None, LinkTypes.None)] + [InlineData(LinkTypes.NotConfigured, LinkTypes.Self, LinkTypes.Self)] + [InlineData(LinkTypes.NotConfigured, LinkTypes.Related, LinkTypes.Related)] + [InlineData(LinkTypes.NotConfigured, LinkTypes.Paging, LinkTypes.Paging)] + [InlineData(LinkTypes.NotConfigured, LinkTypes.All, LinkTypes.All)] + [InlineData(LinkTypes.None, LinkTypes.NotConfigured, LinkTypes.None)] + [InlineData(LinkTypes.None, LinkTypes.None, LinkTypes.None)] + [InlineData(LinkTypes.None, LinkTypes.Self, LinkTypes.None)] + [InlineData(LinkTypes.None, LinkTypes.Related, LinkTypes.None)] + [InlineData(LinkTypes.None, LinkTypes.Paging, LinkTypes.None)] + [InlineData(LinkTypes.None, LinkTypes.All, LinkTypes.None)] + [InlineData(LinkTypes.Self, LinkTypes.NotConfigured, LinkTypes.Self)] + [InlineData(LinkTypes.Self, LinkTypes.None, LinkTypes.Self)] + [InlineData(LinkTypes.Self, LinkTypes.Self, LinkTypes.Self)] + [InlineData(LinkTypes.Self, LinkTypes.Related, LinkTypes.Self)] + [InlineData(LinkTypes.Self, LinkTypes.Paging, LinkTypes.Self)] + [InlineData(LinkTypes.Self, LinkTypes.All, LinkTypes.Self)] + [InlineData(LinkTypes.Related, LinkTypes.NotConfigured, LinkTypes.Related)] + [InlineData(LinkTypes.Related, LinkTypes.None, LinkTypes.Related)] + [InlineData(LinkTypes.Related, LinkTypes.Self, LinkTypes.Related)] + [InlineData(LinkTypes.Related, LinkTypes.Related, LinkTypes.Related)] + [InlineData(LinkTypes.Related, LinkTypes.Paging, LinkTypes.Related)] + [InlineData(LinkTypes.Related, LinkTypes.All, LinkTypes.Related)] + [InlineData(LinkTypes.Paging, LinkTypes.NotConfigured, LinkTypes.Paging)] + [InlineData(LinkTypes.Paging, LinkTypes.None, LinkTypes.Paging)] + [InlineData(LinkTypes.Paging, LinkTypes.Self, LinkTypes.Paging)] + [InlineData(LinkTypes.Paging, LinkTypes.Related, LinkTypes.Paging)] + [InlineData(LinkTypes.Paging, LinkTypes.Paging, LinkTypes.Paging)] + [InlineData(LinkTypes.Paging, LinkTypes.All, LinkTypes.Paging)] + [InlineData(LinkTypes.All, LinkTypes.NotConfigured, LinkTypes.All)] + [InlineData(LinkTypes.All, LinkTypes.None, LinkTypes.All)] + [InlineData(LinkTypes.All, LinkTypes.Self, LinkTypes.All)] + [InlineData(LinkTypes.All, LinkTypes.Related, LinkTypes.All)] + [InlineData(LinkTypes.All, LinkTypes.Paging, LinkTypes.All)] + [InlineData(LinkTypes.All, LinkTypes.All, LinkTypes.All)] + public void Applies_cascading_settings_for_top_level_links(LinkTypes linksInResourceContext, LinkTypes linksInOptions, LinkTypes expected) + { + // Arrange + var exampleResourceContext = new ResourceContext + { + PublicName = nameof(ExampleResource), + ResourceType = typeof(ExampleResource), + TopLevelLinks = linksInResourceContext + }; + + var resourceGraph = new ResourceGraph(new[] + { + exampleResourceContext + }); + + var request = new JsonApiRequest + { + PrimaryResource = exampleResourceContext, + PrimaryId = "1", + IsCollection = true, + Kind = EndpointKind.Relationship, + Relationship = new HasOneAttribute() + }; + + var paginationContext = new PaginationContext + { + PageSize = new PageSize(1), + PageNumber = new PageNumber(2), + TotalResourceCount = 10 + }; + + var queryStringAccessor = new EmptyRequestQueryStringAccessor(); + + var options = new JsonApiOptions + { + TopLevelLinks = linksInOptions + }; + + var linkBuilder = new LinkBuilder(options, request, paginationContext, resourceGraph, queryStringAccessor); + + // Act + var topLevelLinks = linkBuilder.GetTopLevelLinks(); + + // Assert + if (expected == LinkTypes.None) + { + topLevelLinks.Should().BeNull(); + } + else + { + if (expected.HasFlag(LinkTypes.Self)) + { + topLevelLinks.Self.Should().NotBeNull(); + } + else + { + topLevelLinks.Self.Should().BeNull(); + } + + if (expected.HasFlag(LinkTypes.Related)) + { + topLevelLinks.Related.Should().NotBeNull(); + } + else + { + topLevelLinks.Related.Should().BeNull(); + } + + if (expected.HasFlag(LinkTypes.Paging)) + { + topLevelLinks.First.Should().NotBeNull(); + topLevelLinks.Last.Should().NotBeNull(); + topLevelLinks.Prev.Should().NotBeNull(); + topLevelLinks.Next.Should().NotBeNull(); + } + else + { + topLevelLinks.First.Should().BeNull(); + topLevelLinks.Last.Should().BeNull(); + topLevelLinks.Prev.Should().BeNull(); + topLevelLinks.Next.Should().BeNull(); + } + } + } + + [Theory] + [InlineData(LinkTypes.NotConfigured, LinkTypes.NotConfigured, LinkTypes.None)] + [InlineData(LinkTypes.NotConfigured, LinkTypes.None, LinkTypes.None)] + [InlineData(LinkTypes.NotConfigured, LinkTypes.Self, LinkTypes.Self)] + [InlineData(LinkTypes.NotConfigured, LinkTypes.All, LinkTypes.Self)] + [InlineData(LinkTypes.None, LinkTypes.NotConfigured, LinkTypes.None)] + [InlineData(LinkTypes.None, LinkTypes.None, LinkTypes.None)] + [InlineData(LinkTypes.None, LinkTypes.Self, LinkTypes.None)] + [InlineData(LinkTypes.None, LinkTypes.All, LinkTypes.None)] + [InlineData(LinkTypes.Self, LinkTypes.NotConfigured, LinkTypes.Self)] + [InlineData(LinkTypes.Self, LinkTypes.None, LinkTypes.Self)] + [InlineData(LinkTypes.Self, LinkTypes.Self, LinkTypes.Self)] + [InlineData(LinkTypes.Self, LinkTypes.All, LinkTypes.Self)] + [InlineData(LinkTypes.All, LinkTypes.NotConfigured, LinkTypes.Self)] + [InlineData(LinkTypes.All, LinkTypes.None, LinkTypes.Self)] + [InlineData(LinkTypes.All, LinkTypes.Self, LinkTypes.Self)] + [InlineData(LinkTypes.All, LinkTypes.All, LinkTypes.Self)] + public void Applies_cascading_settings_for_resource_links(LinkTypes linksInResourceContext, LinkTypes linksInOptions, LinkTypes expected) + { + // Arrange + var exampleResourceContext = new ResourceContext + { + PublicName = nameof(ExampleResource), + ResourceType = typeof(ExampleResource), + ResourceLinks = linksInResourceContext + }; + + var resourceGraph = new ResourceGraph(new[] + { + exampleResourceContext + }); + + var request = new JsonApiRequest(); + + var paginationContext = new PaginationContext(); + + var queryStringAccessor = new EmptyRequestQueryStringAccessor(); + + var options = new JsonApiOptions + { + ResourceLinks = linksInOptions + }; + + var linkBuilder = new LinkBuilder(options, request, paginationContext, resourceGraph, queryStringAccessor); + + // Act + var resourceLinks = linkBuilder.GetResourceLinks(nameof(ExampleResource), "id"); + + // Assert + if (expected == LinkTypes.Self) + { + resourceLinks.Self.Should().NotBeNull(); + } + else + { + resourceLinks.Should().BeNull(); + } + } + + [Theory] + [InlineData(LinkTypes.NotConfigured, LinkTypes.NotConfigured, LinkTypes.NotConfigured, LinkTypes.None)] + [InlineData(LinkTypes.NotConfigured, LinkTypes.NotConfigured, LinkTypes.None, LinkTypes.None)] + [InlineData(LinkTypes.NotConfigured, LinkTypes.NotConfigured, LinkTypes.Self, LinkTypes.Self)] + [InlineData(LinkTypes.NotConfigured, LinkTypes.NotConfigured, LinkTypes.Related, LinkTypes.Related)] + [InlineData(LinkTypes.NotConfigured, LinkTypes.NotConfigured, LinkTypes.All, LinkTypes.All)] + [InlineData(LinkTypes.NotConfigured, LinkTypes.None, LinkTypes.NotConfigured, LinkTypes.None)] + [InlineData(LinkTypes.NotConfigured, LinkTypes.None, LinkTypes.None, LinkTypes.None)] + [InlineData(LinkTypes.NotConfigured, LinkTypes.None, LinkTypes.Self, LinkTypes.None)] + [InlineData(LinkTypes.NotConfigured, LinkTypes.None, LinkTypes.Related, LinkTypes.None)] + [InlineData(LinkTypes.NotConfigured, LinkTypes.None, LinkTypes.All, LinkTypes.None)] + [InlineData(LinkTypes.NotConfigured, LinkTypes.Self, LinkTypes.NotConfigured, LinkTypes.Self)] + [InlineData(LinkTypes.NotConfigured, LinkTypes.Self, LinkTypes.None, LinkTypes.Self)] + [InlineData(LinkTypes.NotConfigured, LinkTypes.Self, LinkTypes.Self, LinkTypes.Self)] + [InlineData(LinkTypes.NotConfigured, LinkTypes.Self, LinkTypes.Related, LinkTypes.Self)] + [InlineData(LinkTypes.NotConfigured, LinkTypes.Self, LinkTypes.All, LinkTypes.Self)] + [InlineData(LinkTypes.NotConfigured, LinkTypes.Related, LinkTypes.NotConfigured, LinkTypes.Related)] + [InlineData(LinkTypes.NotConfigured, LinkTypes.Related, LinkTypes.None, LinkTypes.Related)] + [InlineData(LinkTypes.NotConfigured, LinkTypes.Related, LinkTypes.Self, LinkTypes.Related)] + [InlineData(LinkTypes.NotConfigured, LinkTypes.Related, LinkTypes.Related, LinkTypes.Related)] + [InlineData(LinkTypes.NotConfigured, LinkTypes.Related, LinkTypes.All, LinkTypes.Related)] + [InlineData(LinkTypes.NotConfigured, LinkTypes.All, LinkTypes.NotConfigured, LinkTypes.All)] + [InlineData(LinkTypes.NotConfigured, LinkTypes.All, LinkTypes.None, LinkTypes.All)] + [InlineData(LinkTypes.NotConfigured, LinkTypes.All, LinkTypes.Self, LinkTypes.All)] + [InlineData(LinkTypes.NotConfigured, LinkTypes.All, LinkTypes.Related, LinkTypes.All)] + [InlineData(LinkTypes.NotConfigured, LinkTypes.All, LinkTypes.All, LinkTypes.All)] + [InlineData(LinkTypes.None, LinkTypes.NotConfigured, LinkTypes.NotConfigured, LinkTypes.None)] + [InlineData(LinkTypes.None, LinkTypes.NotConfigured, LinkTypes.None, LinkTypes.None)] + [InlineData(LinkTypes.None, LinkTypes.NotConfigured, LinkTypes.Self, LinkTypes.None)] + [InlineData(LinkTypes.None, LinkTypes.NotConfigured, LinkTypes.Related, LinkTypes.None)] + [InlineData(LinkTypes.None, LinkTypes.NotConfigured, LinkTypes.All, LinkTypes.None)] + [InlineData(LinkTypes.None, LinkTypes.None, LinkTypes.NotConfigured, LinkTypes.None)] + [InlineData(LinkTypes.None, LinkTypes.None, LinkTypes.None, LinkTypes.None)] + [InlineData(LinkTypes.None, LinkTypes.None, LinkTypes.Self, LinkTypes.None)] + [InlineData(LinkTypes.None, LinkTypes.None, LinkTypes.Related, LinkTypes.None)] + [InlineData(LinkTypes.None, LinkTypes.None, LinkTypes.All, LinkTypes.None)] + [InlineData(LinkTypes.None, LinkTypes.Self, LinkTypes.NotConfigured, LinkTypes.None)] + [InlineData(LinkTypes.None, LinkTypes.Self, LinkTypes.None, LinkTypes.None)] + [InlineData(LinkTypes.None, LinkTypes.Self, LinkTypes.Self, LinkTypes.None)] + [InlineData(LinkTypes.None, LinkTypes.Self, LinkTypes.Related, LinkTypes.None)] + [InlineData(LinkTypes.None, LinkTypes.Self, LinkTypes.All, LinkTypes.None)] + [InlineData(LinkTypes.None, LinkTypes.Related, LinkTypes.NotConfigured, LinkTypes.None)] + [InlineData(LinkTypes.None, LinkTypes.Related, LinkTypes.None, LinkTypes.None)] + [InlineData(LinkTypes.None, LinkTypes.Related, LinkTypes.Self, LinkTypes.None)] + [InlineData(LinkTypes.None, LinkTypes.Related, LinkTypes.Related, LinkTypes.None)] + [InlineData(LinkTypes.None, LinkTypes.Related, LinkTypes.All, LinkTypes.None)] + [InlineData(LinkTypes.None, LinkTypes.All, LinkTypes.NotConfigured, LinkTypes.None)] + [InlineData(LinkTypes.None, LinkTypes.All, LinkTypes.None, LinkTypes.None)] + [InlineData(LinkTypes.None, LinkTypes.All, LinkTypes.Self, LinkTypes.None)] + [InlineData(LinkTypes.None, LinkTypes.All, LinkTypes.Related, LinkTypes.None)] + [InlineData(LinkTypes.None, LinkTypes.All, LinkTypes.All, LinkTypes.None)] + [InlineData(LinkTypes.Self, LinkTypes.NotConfigured, LinkTypes.NotConfigured, LinkTypes.Self)] + [InlineData(LinkTypes.Self, LinkTypes.NotConfigured, LinkTypes.None, LinkTypes.Self)] + [InlineData(LinkTypes.Self, LinkTypes.NotConfigured, LinkTypes.Self, LinkTypes.Self)] + [InlineData(LinkTypes.Self, LinkTypes.NotConfigured, LinkTypes.Related, LinkTypes.Self)] + [InlineData(LinkTypes.Self, LinkTypes.NotConfigured, LinkTypes.All, LinkTypes.Self)] + [InlineData(LinkTypes.Self, LinkTypes.None, LinkTypes.NotConfigured, LinkTypes.Self)] + [InlineData(LinkTypes.Self, LinkTypes.None, LinkTypes.None, LinkTypes.Self)] + [InlineData(LinkTypes.Self, LinkTypes.None, LinkTypes.Self, LinkTypes.Self)] + [InlineData(LinkTypes.Self, LinkTypes.None, LinkTypes.Related, LinkTypes.Self)] + [InlineData(LinkTypes.Self, LinkTypes.None, LinkTypes.All, LinkTypes.Self)] + [InlineData(LinkTypes.Self, LinkTypes.Self, LinkTypes.NotConfigured, LinkTypes.Self)] + [InlineData(LinkTypes.Self, LinkTypes.Self, LinkTypes.None, LinkTypes.Self)] + [InlineData(LinkTypes.Self, LinkTypes.Self, LinkTypes.Self, LinkTypes.Self)] + [InlineData(LinkTypes.Self, LinkTypes.Self, LinkTypes.Related, LinkTypes.Self)] + [InlineData(LinkTypes.Self, LinkTypes.Self, LinkTypes.All, LinkTypes.Self)] + [InlineData(LinkTypes.Self, LinkTypes.Related, LinkTypes.NotConfigured, LinkTypes.Self)] + [InlineData(LinkTypes.Self, LinkTypes.Related, LinkTypes.None, LinkTypes.Self)] + [InlineData(LinkTypes.Self, LinkTypes.Related, LinkTypes.Self, LinkTypes.Self)] + [InlineData(LinkTypes.Self, LinkTypes.Related, LinkTypes.Related, LinkTypes.Self)] + [InlineData(LinkTypes.Self, LinkTypes.Related, LinkTypes.All, LinkTypes.Self)] + [InlineData(LinkTypes.Self, LinkTypes.All, LinkTypes.NotConfigured, LinkTypes.Self)] + [InlineData(LinkTypes.Self, LinkTypes.All, LinkTypes.None, LinkTypes.Self)] + [InlineData(LinkTypes.Self, LinkTypes.All, LinkTypes.Self, LinkTypes.Self)] + [InlineData(LinkTypes.Self, LinkTypes.All, LinkTypes.Related, LinkTypes.Self)] + [InlineData(LinkTypes.Self, LinkTypes.All, LinkTypes.All, LinkTypes.Self)] + [InlineData(LinkTypes.Related, LinkTypes.NotConfigured, LinkTypes.NotConfigured, LinkTypes.Related)] + [InlineData(LinkTypes.Related, LinkTypes.NotConfigured, LinkTypes.None, LinkTypes.Related)] + [InlineData(LinkTypes.Related, LinkTypes.NotConfigured, LinkTypes.Self, LinkTypes.Related)] + [InlineData(LinkTypes.Related, LinkTypes.NotConfigured, LinkTypes.Related, LinkTypes.Related)] + [InlineData(LinkTypes.Related, LinkTypes.NotConfigured, LinkTypes.All, LinkTypes.Related)] + [InlineData(LinkTypes.Related, LinkTypes.None, LinkTypes.NotConfigured, LinkTypes.Related)] + [InlineData(LinkTypes.Related, LinkTypes.None, LinkTypes.None, LinkTypes.Related)] + [InlineData(LinkTypes.Related, LinkTypes.None, LinkTypes.Self, LinkTypes.Related)] + [InlineData(LinkTypes.Related, LinkTypes.None, LinkTypes.Related, LinkTypes.Related)] + [InlineData(LinkTypes.Related, LinkTypes.None, LinkTypes.All, LinkTypes.Related)] + [InlineData(LinkTypes.Related, LinkTypes.Self, LinkTypes.NotConfigured, LinkTypes.Related)] + [InlineData(LinkTypes.Related, LinkTypes.Self, LinkTypes.None, LinkTypes.Related)] + [InlineData(LinkTypes.Related, LinkTypes.Self, LinkTypes.Self, LinkTypes.Related)] + [InlineData(LinkTypes.Related, LinkTypes.Self, LinkTypes.Related, LinkTypes.Related)] + [InlineData(LinkTypes.Related, LinkTypes.Self, LinkTypes.All, LinkTypes.Related)] + [InlineData(LinkTypes.Related, LinkTypes.Related, LinkTypes.NotConfigured, LinkTypes.Related)] + [InlineData(LinkTypes.Related, LinkTypes.Related, LinkTypes.None, LinkTypes.Related)] + [InlineData(LinkTypes.Related, LinkTypes.Related, LinkTypes.Self, LinkTypes.Related)] + [InlineData(LinkTypes.Related, LinkTypes.Related, LinkTypes.Related, LinkTypes.Related)] + [InlineData(LinkTypes.Related, LinkTypes.Related, LinkTypes.All, LinkTypes.Related)] + [InlineData(LinkTypes.Related, LinkTypes.All, LinkTypes.NotConfigured, LinkTypes.Related)] + [InlineData(LinkTypes.Related, LinkTypes.All, LinkTypes.None, LinkTypes.Related)] + [InlineData(LinkTypes.Related, LinkTypes.All, LinkTypes.Self, LinkTypes.Related)] + [InlineData(LinkTypes.Related, LinkTypes.All, LinkTypes.Related, LinkTypes.Related)] + [InlineData(LinkTypes.Related, LinkTypes.All, LinkTypes.All, LinkTypes.Related)] + [InlineData(LinkTypes.All, LinkTypes.NotConfigured, LinkTypes.NotConfigured, LinkTypes.All)] + [InlineData(LinkTypes.All, LinkTypes.NotConfigured, LinkTypes.None, LinkTypes.All)] + [InlineData(LinkTypes.All, LinkTypes.NotConfigured, LinkTypes.Self, LinkTypes.All)] + [InlineData(LinkTypes.All, LinkTypes.NotConfigured, LinkTypes.Related, LinkTypes.All)] + [InlineData(LinkTypes.All, LinkTypes.NotConfigured, LinkTypes.All, LinkTypes.All)] + [InlineData(LinkTypes.All, LinkTypes.None, LinkTypes.NotConfigured, LinkTypes.All)] + [InlineData(LinkTypes.All, LinkTypes.None, LinkTypes.None, LinkTypes.All)] + [InlineData(LinkTypes.All, LinkTypes.None, LinkTypes.Self, LinkTypes.All)] + [InlineData(LinkTypes.All, LinkTypes.None, LinkTypes.Related, LinkTypes.All)] + [InlineData(LinkTypes.All, LinkTypes.None, LinkTypes.All, LinkTypes.All)] + [InlineData(LinkTypes.All, LinkTypes.Self, LinkTypes.NotConfigured, LinkTypes.All)] + [InlineData(LinkTypes.All, LinkTypes.Self, LinkTypes.None, LinkTypes.All)] + [InlineData(LinkTypes.All, LinkTypes.Self, LinkTypes.Self, LinkTypes.All)] + [InlineData(LinkTypes.All, LinkTypes.Self, LinkTypes.Related, LinkTypes.All)] + [InlineData(LinkTypes.All, LinkTypes.Self, LinkTypes.All, LinkTypes.All)] + [InlineData(LinkTypes.All, LinkTypes.Related, LinkTypes.NotConfigured, LinkTypes.All)] + [InlineData(LinkTypes.All, LinkTypes.Related, LinkTypes.None, LinkTypes.All)] + [InlineData(LinkTypes.All, LinkTypes.Related, LinkTypes.Self, LinkTypes.All)] + [InlineData(LinkTypes.All, LinkTypes.Related, LinkTypes.Related, LinkTypes.All)] + [InlineData(LinkTypes.All, LinkTypes.Related, LinkTypes.All, LinkTypes.All)] + [InlineData(LinkTypes.All, LinkTypes.All, LinkTypes.NotConfigured, LinkTypes.All)] + [InlineData(LinkTypes.All, LinkTypes.All, LinkTypes.None, LinkTypes.All)] + [InlineData(LinkTypes.All, LinkTypes.All, LinkTypes.Self, LinkTypes.All)] + [InlineData(LinkTypes.All, LinkTypes.All, LinkTypes.Related, LinkTypes.All)] + [InlineData(LinkTypes.All, LinkTypes.All, LinkTypes.All, LinkTypes.All)] + public void Applies_cascading_settings_for_relationship_links(LinkTypes linksInRelationshipAttribute, LinkTypes linksInResourceContext, LinkTypes linksInOptions, LinkTypes expected) + { + // Arrange + var exampleResourceContext = new ResourceContext + { + PublicName = nameof(ExampleResource), + ResourceType = typeof(ExampleResource), + RelationshipLinks = linksInResourceContext + }; + + var resourceGraph = new ResourceGraph(new[] + { + exampleResourceContext + }); + + var request = new JsonApiRequest(); + + var paginationContext = new PaginationContext(); + + var queryStringAccessor = new EmptyRequestQueryStringAccessor(); + + var options = new JsonApiOptions + { + RelationshipLinks = linksInOptions + }; + + var linkBuilder = new LinkBuilder(options, request, paginationContext, resourceGraph, queryStringAccessor); + + var relationship = new HasOneAttribute + { + Links = linksInRelationshipAttribute + }; + + // Act + var relationshipLinks = linkBuilder.GetRelationshipLinks(relationship, new ExampleResource()); + + // Assert + if (expected == LinkTypes.None) + { + relationshipLinks.Should().BeNull(); + } + else + { + if (expected.HasFlag(LinkTypes.Self)) + { + relationshipLinks.Self.Should().NotBeNull(); + } + else + { + relationshipLinks.Self.Should().BeNull(); + } + + if (expected.HasFlag(LinkTypes.Related)) + { + relationshipLinks.Related.Should().NotBeNull(); + } + else + { + relationshipLinks.Related.Should().BeNull(); + } + } + } + + private sealed class EmptyRequestQueryStringAccessor : IRequestQueryStringAccessor + { + public IQueryCollection Query { get; } = new QueryCollection(); + } + + private sealed class ExampleResource : Identifiable + { + } + } +} diff --git a/test/UnitTests/Builders/LinkBuilderTests.cs b/test/UnitTests/Builders/LinkBuilderTests.cs deleted file mode 100644 index 3799a6d3bf..0000000000 --- a/test/UnitTests/Builders/LinkBuilderTests.cs +++ /dev/null @@ -1,235 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Middleware; -using JsonApiDotNetCore.Queries; -using JsonApiDotNetCore.QueryStrings; -using JsonApiDotNetCore.Resources.Annotations; -using JsonApiDotNetCore.Serialization.Building; -using JsonApiDotNetCoreExample.Models; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.WebUtilities; -using Moq; -using Xunit; - -namespace UnitTests -{ - public sealed class LinkBuilderTests - { - private readonly IPaginationContext _paginationContext = GetPaginationContext(); - private readonly Mock _provider = new Mock(); - private readonly IRequestQueryStringAccessor _queryStringAccessor = new FakeRequestQueryStringAccessor("?foo=bar&page[size]=10&page[number]=2"); - private const string _host = "http://www.example.com"; - private const int _primaryId = 123; - private const string _relationshipName = "author"; - private const string _topSelf = "http://www.example.com/articles?foo=bar&page[size]=10&page[number]=2"; - private const string _topResourceSelf = "http://www.example.com/articles/123?foo=bar&page[size]=10&page[number]=2"; - private const string _topRelatedSelf = "http://www.example.com/articles/123/author?foo=bar&page[size]=10&page[number]=2"; - private const string _resourceSelf = "http://www.example.com/articles/123"; - private const string _relSelf = "http://www.example.com/articles/123/relationships/author"; - private const string _relRelated = "http://www.example.com/articles/123/author"; - - [Theory] - [InlineData(LinkTypes.All, LinkTypes.NotConfigured, _resourceSelf)] - [InlineData(LinkTypes.Self, LinkTypes.NotConfigured, _resourceSelf)] - [InlineData(LinkTypes.None, LinkTypes.NotConfigured, null)] - [InlineData(LinkTypes.All, LinkTypes.Self, _resourceSelf)] - [InlineData(LinkTypes.Self, LinkTypes.Self, _resourceSelf)] - [InlineData(LinkTypes.None, LinkTypes.Self, _resourceSelf)] - [InlineData(LinkTypes.All, LinkTypes.None, null)] - [InlineData(LinkTypes.Self, LinkTypes.None, null)] - [InlineData(LinkTypes.None, LinkTypes.None, null)] - public void BuildResourceLinks_GlobalAndResourceConfiguration_ExpectedResult(LinkTypes global, LinkTypes resource, object expectedResult) - { - // Arrange - var config = GetConfiguration(resourceLinks: global); - var primaryResource = GetArticleResourceContext(resourceLinks: resource); - _provider.Setup(m => m.GetResourceContext("articles")).Returns(primaryResource); - var builder = new LinkBuilder(config, GetRequestManager(), new PaginationContext(), _provider.Object, _queryStringAccessor); - - // Act - var links = builder.GetResourceLinks("articles", _primaryId.ToString()); - - // Assert - if (expectedResult == null) - Assert.Null(links); - else - Assert.Equal(_resourceSelf, links.Self); - } - - [Theory] - [InlineData(LinkTypes.All, LinkTypes.NotConfigured, LinkTypes.NotConfigured, _relSelf, _relRelated)] - [InlineData(LinkTypes.All, LinkTypes.NotConfigured, LinkTypes.All, _relSelf, _relRelated)] - [InlineData(LinkTypes.All, LinkTypes.NotConfigured, LinkTypes.Self, _relSelf, null)] - [InlineData(LinkTypes.All, LinkTypes.NotConfigured, LinkTypes.Related, null, _relRelated)] - [InlineData(LinkTypes.All, LinkTypes.NotConfigured, LinkTypes.None, null, null)] - [InlineData(LinkTypes.All, LinkTypes.All, LinkTypes.NotConfigured, _relSelf, _relRelated)] - [InlineData(LinkTypes.All, LinkTypes.All, LinkTypes.All, _relSelf, _relRelated)] - [InlineData(LinkTypes.All, LinkTypes.All, LinkTypes.Self, _relSelf, null)] - [InlineData(LinkTypes.All, LinkTypes.All, LinkTypes.Related, null, _relRelated)] - [InlineData(LinkTypes.All, LinkTypes.All, LinkTypes.None, null, null)] - [InlineData(LinkTypes.All, LinkTypes.Self, LinkTypes.NotConfigured, _relSelf, null)] - [InlineData(LinkTypes.All, LinkTypes.Self, LinkTypes.All, _relSelf, _relRelated)] - [InlineData(LinkTypes.All, LinkTypes.Self, LinkTypes.Self, _relSelf, null)] - [InlineData(LinkTypes.All, LinkTypes.Self, LinkTypes.Related, null, _relRelated)] - [InlineData(LinkTypes.All, LinkTypes.Self, LinkTypes.None, null, null)] - [InlineData(LinkTypes.All, LinkTypes.Related, LinkTypes.NotConfigured, null, _relRelated)] - [InlineData(LinkTypes.All, LinkTypes.Related, LinkTypes.All, _relSelf, _relRelated)] - [InlineData(LinkTypes.All, LinkTypes.Related, LinkTypes.Self, _relSelf, null)] - [InlineData(LinkTypes.All, LinkTypes.Related, LinkTypes.Related, null, _relRelated)] - [InlineData(LinkTypes.All, LinkTypes.Related, LinkTypes.None, null, null)] - [InlineData(LinkTypes.All, LinkTypes.None, LinkTypes.NotConfigured, null, null)] - [InlineData(LinkTypes.All, LinkTypes.None, LinkTypes.All, _relSelf, _relRelated)] - [InlineData(LinkTypes.All, LinkTypes.None, LinkTypes.Self, _relSelf, null)] - [InlineData(LinkTypes.All, LinkTypes.None, LinkTypes.Related, null, _relRelated)] - [InlineData(LinkTypes.All, LinkTypes.None, LinkTypes.None, null, null)] - public void BuildRelationshipLinks_GlobalResourceAndAttrConfiguration_ExpectedLinks( - LinkTypes global, LinkTypes resource, LinkTypes relationship, object expectedSelfLink, object expectedRelatedLink) - { - // Arrange - var config = GetConfiguration(relationshipLinks: global); - var primaryResource = GetArticleResourceContext(relationshipLinks: resource); - _provider.Setup(m => m.GetResourceContext(typeof(Article))).Returns(primaryResource); - var builder = new LinkBuilder(config, GetRequestManager(), new PaginationContext(), _provider.Object, _queryStringAccessor); - var attr = new HasOneAttribute { Links = relationship, RightType = typeof(Author), PublicName = "author" }; - - // Act - var links = builder.GetRelationshipLinks(attr, new Article { Id = _primaryId }); - - // Assert - if (expectedSelfLink == null && expectedRelatedLink == null) - { - Assert.Null(links); - } - else - { - Assert.Equal(expectedSelfLink, links.Self); - Assert.Equal(expectedRelatedLink, links.Related); - } - } - - [Theory] - [InlineData(LinkTypes.All, LinkTypes.NotConfigured, _topSelf, true)] - [InlineData(LinkTypes.All, LinkTypes.All, _topSelf, true)] - [InlineData(LinkTypes.All, LinkTypes.Self, _topSelf, false)] - [InlineData(LinkTypes.All, LinkTypes.Paging, null, true)] - [InlineData(LinkTypes.All, LinkTypes.None, null, false)] - [InlineData(LinkTypes.Self, LinkTypes.NotConfigured, _topSelf, false)] - [InlineData(LinkTypes.Self, LinkTypes.All, _topSelf, true)] - [InlineData(LinkTypes.Self, LinkTypes.Self, _topSelf, false)] - [InlineData(LinkTypes.Self, LinkTypes.Paging, null, true)] - [InlineData(LinkTypes.Self, LinkTypes.None, null, false)] - [InlineData(LinkTypes.Paging, LinkTypes.NotConfigured, null, true)] - [InlineData(LinkTypes.Paging, LinkTypes.All, _topSelf, true)] - [InlineData(LinkTypes.Paging, LinkTypes.Self, _topSelf, false)] - [InlineData(LinkTypes.Paging, LinkTypes.Paging, null, true)] - [InlineData(LinkTypes.Paging, LinkTypes.None, null, false)] - [InlineData(LinkTypes.None, LinkTypes.NotConfigured, null, false)] - [InlineData(LinkTypes.None, LinkTypes.All, _topSelf, true)] - [InlineData(LinkTypes.None, LinkTypes.Self, _topSelf, false)] - [InlineData(LinkTypes.None, LinkTypes.Paging, null, true)] - [InlineData(LinkTypes.None, LinkTypes.None, null, false)] - [InlineData(LinkTypes.All, LinkTypes.Self, _topResourceSelf, false)] - [InlineData(LinkTypes.Self, LinkTypes.Self, _topResourceSelf, false)] - [InlineData(LinkTypes.Paging, LinkTypes.Self, _topResourceSelf, false)] - [InlineData(LinkTypes.None, LinkTypes.Self, _topResourceSelf, false)] - [InlineData(LinkTypes.All, LinkTypes.Self, _topRelatedSelf, false)] - [InlineData(LinkTypes.Self, LinkTypes.Self, _topRelatedSelf, false)] - [InlineData(LinkTypes.Paging, LinkTypes.Self, _topRelatedSelf, false)] - [InlineData(LinkTypes.None, LinkTypes.Self, _topRelatedSelf, false)] - public void BuildTopLevelLinks_GlobalAndResourceConfiguration_ExpectedLinks( - LinkTypes global, LinkTypes resource, string expectedSelfLink, bool pages) - { - // Arrange - var config = GetConfiguration(topLevelLinks: global); - var primaryResource = GetArticleResourceContext(topLevelLinks: resource); - _provider.Setup(m => m.GetResourceContext
()).Returns(primaryResource); - - bool usePrimaryId = expectedSelfLink != null && expectedSelfLink.Contains("123"); - string relationshipName = expectedSelfLink == _topRelatedSelf ? _relationshipName : null; - IJsonApiRequest request = GetRequestManager(primaryResource, usePrimaryId, relationshipName); - - var builder = new LinkBuilder(config, request, _paginationContext, _provider.Object, _queryStringAccessor); - - // Act - var links = builder.GetTopLevelLinks(); - - // Assert - if (!pages && expectedSelfLink == null) - { - Assert.Null(links); - } - else - { - Assert.Equal(links.Self, expectedSelfLink); - - if (pages) - { - Assert.Equal($"{_host}/articles?foo=bar&page[size]=10", links.First); - Assert.Equal($"{_host}/articles?foo=bar&page[size]=10", links.Prev); - Assert.Equal($"{_host}/articles?foo=bar&page[size]=10&page[number]=3", links.Next); - Assert.Equal($"{_host}/articles?foo=bar&page[size]=10&page[number]=3", links.Last); - } - else - { - Assert.Null(links.First); - Assert.Null(links.Prev); - Assert.Null(links.Next); - Assert.Null(links.Last); - } - } - } - - private IJsonApiRequest GetRequestManager(ResourceContext resourceContext = null, bool usePrimaryId = false, string relationshipName = null) - { - var mock = new Mock(); - mock.Setup(m => m.BasePath).Returns(_host); - mock.Setup(m => m.PrimaryId).Returns(usePrimaryId ? _primaryId.ToString() : null); - mock.Setup(m => m.Relationship).Returns(relationshipName != null ? new HasOneAttribute {PublicName = relationshipName} : null); - mock.Setup(m => m.PrimaryResource).Returns(resourceContext); - mock.Setup(m => m.IsCollection).Returns(true); - return mock.Object; - } - - private IJsonApiOptions GetConfiguration(LinkTypes resourceLinks = LinkTypes.All, LinkTypes topLevelLinks = LinkTypes.All, LinkTypes relationshipLinks = LinkTypes.All) - { - var config = new Mock(); - config.Setup(m => m.TopLevelLinks).Returns(topLevelLinks); - config.Setup(m => m.ResourceLinks).Returns(resourceLinks); - config.Setup(m => m.RelationshipLinks).Returns(relationshipLinks); - config.Setup(m => m.DefaultPageSize).Returns(new PageSize(25)); - return config.Object; - } - - private static IPaginationContext GetPaginationContext() - { - var mock = new Mock(); - mock.Setup(x => x.PageNumber).Returns(new PageNumber(2)); - mock.Setup(x => x.PageSize).Returns(new PageSize(10)); - mock.Setup(x => x.TotalPageCount).Returns(3); - - return mock.Object; - } - - private ResourceContext GetArticleResourceContext(LinkTypes resourceLinks = LinkTypes.NotConfigured, - LinkTypes topLevelLinks = LinkTypes.NotConfigured, - LinkTypes relationshipLinks = LinkTypes.NotConfigured) - { - return new ResourceContext - { - ResourceLinks = resourceLinks, - TopLevelLinks = topLevelLinks, - RelationshipLinks = relationshipLinks, - PublicName = "articles" - }; - } - - private sealed class FakeRequestQueryStringAccessor : IRequestQueryStringAccessor - { - public IQueryCollection Query { get; } - - public FakeRequestQueryStringAccessor(string queryString) - { - Query = new QueryCollection(QueryHelpers.ParseQuery(queryString)); - } - } - } -} diff --git a/test/UnitTests/Builders/LinkTests.cs b/test/UnitTests/Builders/LinkTests.cs deleted file mode 100644 index 1cf5501ec0..0000000000 --- a/test/UnitTests/Builders/LinkTests.cs +++ /dev/null @@ -1,38 +0,0 @@ -using JsonApiDotNetCore.Resources.Annotations; -using Xunit; - -namespace UnitTests.Builders -{ - public sealed class LinkTests - { - [Theory] - [InlineData(LinkTypes.All, LinkTypes.Self, true)] - [InlineData(LinkTypes.All, LinkTypes.Related, true)] - [InlineData(LinkTypes.All, LinkTypes.Paging, true)] - [InlineData(LinkTypes.None, LinkTypes.Self, false)] - [InlineData(LinkTypes.None, LinkTypes.Related, false)] - [InlineData(LinkTypes.None, LinkTypes.Paging, false)] - [InlineData(LinkTypes.NotConfigured, LinkTypes.Self, false)] - [InlineData(LinkTypes.NotConfigured, LinkTypes.Related, false)] - [InlineData(LinkTypes.NotConfigured, LinkTypes.Paging, false)] - [InlineData(LinkTypes.Self, LinkTypes.Self, true)] - [InlineData(LinkTypes.Self, LinkTypes.Related, false)] - [InlineData(LinkTypes.Self, LinkTypes.Paging, false)] - [InlineData(LinkTypes.Self, LinkTypes.None, false)] - [InlineData(LinkTypes.Self, LinkTypes.NotConfigured, false)] - [InlineData(LinkTypes.Related, LinkTypes.Self, false)] - [InlineData(LinkTypes.Related, LinkTypes.Related, true)] - [InlineData(LinkTypes.Related, LinkTypes.Paging, false)] - [InlineData(LinkTypes.Related, LinkTypes.None, false)] - [InlineData(LinkTypes.Related, LinkTypes.NotConfigured, false)] - [InlineData(LinkTypes.Paging, LinkTypes.Self, false)] - [InlineData(LinkTypes.Paging, LinkTypes.Related, false)] - [InlineData(LinkTypes.Paging, LinkTypes.Paging, true)] - [InlineData(LinkTypes.Paging, LinkTypes.None, false)] - [InlineData(LinkTypes.Paging, LinkTypes.NotConfigured, false)] - public void LinkHasFlag_BaseLinkAndCheckLink_ExpectedResult(LinkTypes baseLink, LinkTypes checkLink, bool equal) - { - Assert.Equal(equal, baseLink.HasFlag(checkLink)); - } - } -} diff --git a/test/UnitTests/Models/LinkTests.cs b/test/UnitTests/Models/LinkTests.cs deleted file mode 100644 index cda2699d39..0000000000 --- a/test/UnitTests/Models/LinkTests.cs +++ /dev/null @@ -1,78 +0,0 @@ -using JsonApiDotNetCore.Resources.Annotations; -using Xunit; - -namespace UnitTests.Models -{ - public sealed class LinkTests - { - [Fact] - public void All_Contains_All_Flags_Except_None() - { - // Arrange - var e = LinkTypes.All; - - // Assert - Assert.True(e.HasFlag(LinkTypes.Self)); - Assert.True(e.HasFlag(LinkTypes.Paging)); - Assert.True(e.HasFlag(LinkTypes.Related)); - Assert.True(e.HasFlag(LinkTypes.All)); - Assert.False(e.HasFlag(LinkTypes.None)); - } - - [Fact] - public void None_Contains_Only_None() - { - // Arrange - var e = LinkTypes.None; - - // Assert - Assert.False(e.HasFlag(LinkTypes.Self)); - Assert.False(e.HasFlag(LinkTypes.Paging)); - Assert.False(e.HasFlag(LinkTypes.Related)); - Assert.False(e.HasFlag(LinkTypes.All)); - Assert.True(e.HasFlag(LinkTypes.None)); - } - - [Fact] - public void Self() - { - // Arrange - var e = LinkTypes.Self; - - // Assert - Assert.True(e.HasFlag(LinkTypes.Self)); - Assert.False(e.HasFlag(LinkTypes.Paging)); - Assert.False(e.HasFlag(LinkTypes.Related)); - Assert.False(e.HasFlag(LinkTypes.All)); - Assert.False(e.HasFlag(LinkTypes.None)); - } - - [Fact] - public void Paging() - { - // Arrange - var e = LinkTypes.Paging; - - // Assert - Assert.False(e.HasFlag(LinkTypes.Self)); - Assert.True(e.HasFlag(LinkTypes.Paging)); - Assert.False(e.HasFlag(LinkTypes.Related)); - Assert.False(e.HasFlag(LinkTypes.All)); - Assert.False(e.HasFlag(LinkTypes.None)); - } - - [Fact] - public void Related() - { - // Arrange - var e = LinkTypes.Related; - - // Assert - Assert.False(e.HasFlag(LinkTypes.Self)); - Assert.False(e.HasFlag(LinkTypes.Paging)); - Assert.True(e.HasFlag(LinkTypes.Related)); - Assert.False(e.HasFlag(LinkTypes.All)); - Assert.False(e.HasFlag(LinkTypes.None)); - } - } -}