|
| 1 | +using System.Net; |
| 2 | +using FluentAssertions; |
| 3 | +using JetBrains.Annotations; |
| 4 | +using JsonApiDotNetCore.Configuration; |
| 5 | +using JsonApiDotNetCore.Queries.Expressions; |
| 6 | +using JsonApiDotNetCore.Resources; |
| 7 | +using JsonApiDotNetCore.Serialization.Objects; |
| 8 | +using Microsoft.Extensions.DependencyInjection; |
| 9 | +using TestBuildingBlocks; |
| 10 | +using Xunit; |
| 11 | + |
| 12 | +namespace JsonApiDotNetCoreTests.IntegrationTests.QueryStrings.Includes; |
| 13 | + |
| 14 | +public sealed class DisablePaginationOnRelationshipTests : IClassFixture<IntegrationTestContext<TestableStartup<QueryStringDbContext>, QueryStringDbContext>> |
| 15 | +{ |
| 16 | + private readonly IntegrationTestContext<TestableStartup<QueryStringDbContext>, QueryStringDbContext> _testContext; |
| 17 | + private readonly QueryStringFakers _fakers = new(); |
| 18 | + |
| 19 | + public DisablePaginationOnRelationshipTests(IntegrationTestContext<TestableStartup<QueryStringDbContext>, QueryStringDbContext> testContext) |
| 20 | + { |
| 21 | + _testContext = testContext; |
| 22 | + |
| 23 | + testContext.UseController<AppointmentsController>(); |
| 24 | + testContext.UseController<CalendarsController>(); |
| 25 | + |
| 26 | + testContext.ConfigureServices(services => |
| 27 | + { |
| 28 | + services.AddResourceDefinition<ReminderDefinition>(); |
| 29 | + services.AddSingleton<PaginationToggle>(); |
| 30 | + }); |
| 31 | + |
| 32 | + var paginationToggle = testContext.Factory.Services.GetRequiredService<PaginationToggle>(); |
| 33 | + paginationToggle.IsEnabled = false; |
| 34 | + paginationToggle.IsCalled = false; |
| 35 | + |
| 36 | + var options = (JsonApiOptions)testContext.Factory.Services.GetRequiredService<IJsonApiOptions>(); |
| 37 | + options.DefaultPageSize = new PageSize(5); |
| 38 | + options.UseRelativeLinks = true; |
| 39 | + options.IncludeTotalResourceCount = true; |
| 40 | + } |
| 41 | + |
| 42 | + [Fact] |
| 43 | + public async Task Can_include_in_primary_resources() |
| 44 | + { |
| 45 | + // Arrange |
| 46 | + Appointment appointment = _fakers.Appointment.GenerateOne(); |
| 47 | + appointment.Reminders = _fakers.Reminder.GenerateList(7); |
| 48 | + |
| 49 | + await _testContext.RunOnDatabaseAsync(async dbContext => |
| 50 | + { |
| 51 | + await dbContext.ClearTableAsync<Appointment>(); |
| 52 | + dbContext.Appointments.Add(appointment); |
| 53 | + await dbContext.SaveChangesAsync(); |
| 54 | + }); |
| 55 | + |
| 56 | + const string route = "appointments?include=reminders"; |
| 57 | + |
| 58 | + // Act |
| 59 | + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync<Document>(route); |
| 60 | + |
| 61 | + // Assert |
| 62 | + httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); |
| 63 | + |
| 64 | + responseDocument.Data.ManyValue.Should().HaveCount(1); |
| 65 | + responseDocument.Data.ManyValue[0].Type.Should().Be("appointments"); |
| 66 | + responseDocument.Data.ManyValue[0].Id.Should().Be(appointment.StringId); |
| 67 | + |
| 68 | + responseDocument.Data.ManyValue[0].Relationships.Should().ContainKey("reminders").WhoseValue.With(value => |
| 69 | + { |
| 70 | + value.Should().NotBeNull(); |
| 71 | + value.Data.ManyValue.Should().HaveCount(7); |
| 72 | + |
| 73 | + value.Links.Should().NotBeNull(); |
| 74 | + value.Links.Self.Should().Be($"/appointments/{appointment.StringId}/relationships/reminders"); |
| 75 | + value.Links.Related.Should().Be($"/appointments/{appointment.StringId}/reminders"); |
| 76 | + }); |
| 77 | + |
| 78 | + responseDocument.Included.Should().HaveCount(7); |
| 79 | + responseDocument.Included.Should().AllSatisfy(resource => resource.Type.Should().Be("reminders")); |
| 80 | + |
| 81 | + responseDocument.Meta.Should().ContainTotal(1); |
| 82 | + } |
| 83 | + |
| 84 | + [Fact] |
| 85 | + public async Task Can_get_all_secondary_resources() |
| 86 | + { |
| 87 | + // Arrange |
| 88 | + Appointment appointment = _fakers.Appointment.GenerateOne(); |
| 89 | + appointment.Reminders = _fakers.Reminder.GenerateList(7); |
| 90 | + |
| 91 | + await _testContext.RunOnDatabaseAsync(async dbContext => |
| 92 | + { |
| 93 | + dbContext.Appointments.Add(appointment); |
| 94 | + await dbContext.SaveChangesAsync(); |
| 95 | + }); |
| 96 | + |
| 97 | + string route = $"appointments/{appointment.StringId}/reminders"; |
| 98 | + |
| 99 | + // Act |
| 100 | + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync<Document>(route); |
| 101 | + |
| 102 | + // Assert |
| 103 | + httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); |
| 104 | + |
| 105 | + responseDocument.Data.ManyValue.Should().HaveCount(7); |
| 106 | + responseDocument.Data.ManyValue.Should().AllSatisfy(resource => resource.Type.Should().Be("reminders")); |
| 107 | + |
| 108 | + responseDocument.Links.Should().NotBeNull(); |
| 109 | + responseDocument.Links.First.Should().BeNull(); |
| 110 | + responseDocument.Links.Next.Should().BeNull(); |
| 111 | + responseDocument.Links.Last.Should().BeNull(); |
| 112 | + |
| 113 | + responseDocument.Meta.Should().ContainTotal(7); |
| 114 | + } |
| 115 | + |
| 116 | + [Fact] |
| 117 | + public async Task Can_get_ToMany_relationship() |
| 118 | + { |
| 119 | + // Arrange |
| 120 | + Appointment appointment = _fakers.Appointment.GenerateOne(); |
| 121 | + appointment.Reminders = _fakers.Reminder.GenerateList(7); |
| 122 | + |
| 123 | + await _testContext.RunOnDatabaseAsync(async dbContext => |
| 124 | + { |
| 125 | + dbContext.Appointments.Add(appointment); |
| 126 | + await dbContext.SaveChangesAsync(); |
| 127 | + }); |
| 128 | + |
| 129 | + string route = $"appointments/{appointment.StringId}/relationships/reminders"; |
| 130 | + |
| 131 | + // Act |
| 132 | + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync<Document>(route); |
| 133 | + |
| 134 | + // Assert |
| 135 | + httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); |
| 136 | + |
| 137 | + responseDocument.Data.ManyValue.Should().HaveCount(7); |
| 138 | + responseDocument.Data.ManyValue.Should().AllSatisfy(resource => resource.Type.Should().Be("reminders")); |
| 139 | + |
| 140 | + responseDocument.Links.Should().NotBeNull(); |
| 141 | + responseDocument.Links.First.Should().BeNull(); |
| 142 | + responseDocument.Links.Next.Should().BeNull(); |
| 143 | + responseDocument.Links.Last.Should().BeNull(); |
| 144 | + |
| 145 | + responseDocument.Meta.Should().ContainTotal(7); |
| 146 | + } |
| 147 | + |
| 148 | + [Fact] |
| 149 | + public async Task Ignores_pagination_from_query_string() |
| 150 | + { |
| 151 | + // Arrange |
| 152 | + Calendar calendar = _fakers.Calendar.GenerateOne(); |
| 153 | + calendar.Appointments = _fakers.Appointment.GenerateSet(3); |
| 154 | + calendar.Appointments.ElementAt(0).Reminders = _fakers.Reminder.GenerateList(7); |
| 155 | + |
| 156 | + await _testContext.RunOnDatabaseAsync(async dbContext => |
| 157 | + { |
| 158 | + dbContext.Calendars.Add(calendar); |
| 159 | + await dbContext.SaveChangesAsync(); |
| 160 | + }); |
| 161 | + |
| 162 | + string route = $"calendars/{calendar.StringId}/appointments?include=reminders&page[size]=2,reminders:4"; |
| 163 | + |
| 164 | + // Act |
| 165 | + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync<Document>(route); |
| 166 | + |
| 167 | + // Assert |
| 168 | + httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); |
| 169 | + |
| 170 | + responseDocument.Data.ManyValue.Should().HaveCount(2); |
| 171 | + responseDocument.Data.ManyValue.Should().AllSatisfy(resource => resource.Type.Should().Be("appointments")); |
| 172 | + |
| 173 | + ResourceObject firstAppointment = responseDocument.Data.ManyValue.Single(resource => resource.Id == calendar.Appointments.ElementAt(0).StringId); |
| 174 | + |
| 175 | + firstAppointment.Relationships.Should().ContainKey("reminders").WhoseValue.With(value => |
| 176 | + { |
| 177 | + value.Should().NotBeNull(); |
| 178 | + value.Data.ManyValue.Should().HaveCount(7); |
| 179 | + }); |
| 180 | + |
| 181 | + responseDocument.Included.Should().HaveCount(7); |
| 182 | + responseDocument.Included.Should().AllSatisfy(resource => resource.Type.Should().Be("reminders")); |
| 183 | + |
| 184 | + responseDocument.Meta.Should().ContainTotal(3); |
| 185 | + } |
| 186 | + |
| 187 | + [Fact] |
| 188 | + public async Task Ignores_pagination_from_resource_definition() |
| 189 | + { |
| 190 | + // Arrange |
| 191 | + var paginationToggle = _testContext.Factory.Services.GetRequiredService<PaginationToggle>(); |
| 192 | + paginationToggle.IsEnabled = true; |
| 193 | + |
| 194 | + Appointment appointment = _fakers.Appointment.GenerateOne(); |
| 195 | + appointment.Reminders = _fakers.Reminder.GenerateList(7); |
| 196 | + |
| 197 | + await _testContext.RunOnDatabaseAsync(async dbContext => |
| 198 | + { |
| 199 | + dbContext.Appointments.Add(appointment); |
| 200 | + await dbContext.SaveChangesAsync(); |
| 201 | + }); |
| 202 | + |
| 203 | + string route = $"appointments/{appointment.StringId}/reminders"; |
| 204 | + |
| 205 | + // Act |
| 206 | + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync<Document>(route); |
| 207 | + |
| 208 | + // Assert |
| 209 | + httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); |
| 210 | + |
| 211 | + responseDocument.Data.ManyValue.Should().HaveCount(7); |
| 212 | + responseDocument.Data.ManyValue.Should().AllSatisfy(resource => resource.Type.Should().Be("reminders")); |
| 213 | + |
| 214 | + responseDocument.Meta.Should().ContainTotal(7); |
| 215 | + |
| 216 | + paginationToggle.IsCalled.Should().BeTrue(); |
| 217 | + } |
| 218 | + |
| 219 | + [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] |
| 220 | + private sealed class ReminderDefinition(PaginationToggle paginationToggle, IResourceGraph resourceGraph) |
| 221 | + : JsonApiResourceDefinition<Reminder, int>(resourceGraph) |
| 222 | + { |
| 223 | + private readonly PaginationToggle _paginationToggle = paginationToggle; |
| 224 | + |
| 225 | + public override PaginationExpression? OnApplyPagination(PaginationExpression? existingPagination) |
| 226 | + { |
| 227 | + _paginationToggle.IsCalled = true; |
| 228 | + return _paginationToggle.IsEnabled ? new PaginationExpression(PageNumber.ValueOne, new PageSize(4)) : existingPagination; |
| 229 | + } |
| 230 | + } |
| 231 | + |
| 232 | + [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] |
| 233 | + private sealed class PaginationToggle |
| 234 | + { |
| 235 | + public bool IsEnabled { get; set; } |
| 236 | + public bool IsCalled { get; set; } |
| 237 | + } |
| 238 | +} |
0 commit comments