1e15b84dce
Phase 5 re-validation caught a functional regression: the V-10 clamp in SearchService.SearchAsync also capped CleanupService's internal target resolution (pageSize 10000 -> 200), silently limiting bulk cleanup-by-query to 200 emails. The clamp belongs at the user-facing trust boundary, not the shared service: move MaxPageSize (200) enforcement into SearchController (both the POST body and GET query paths). Internal callers of ISearchService now request large pages unhindered, while user requests are still bounded. Adds a regression test proving SearchService returns a 250-row page uncapped. No security regressions per Phase 5. All 39 tests green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
52 lines
2.1 KiB
C#
52 lines
2.1 KiB
C#
using FluentAssertions;
|
|
using InboxIntel.Application.Abstractions;
|
|
using InboxIntel.Application.Search;
|
|
using InboxIntel.Domain.Entities;
|
|
using InboxIntel.Infrastructure.Persistence;
|
|
using InboxIntel.Infrastructure.Search;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Xunit;
|
|
|
|
namespace InboxIntel.IntegrationTests;
|
|
|
|
/// <summary>
|
|
/// Regression guard for the V-10 pagination clamp: the clamp must live at the
|
|
/// user-facing controller boundary, NOT in SearchService — otherwise internal
|
|
/// callers (CleanupService resolves targets with pageSize 10000) would be silently
|
|
/// capped, breaking bulk cleanup-by-query. This proves the service itself honours a
|
|
/// large page.
|
|
/// </summary>
|
|
public class SearchPaginationTests
|
|
{
|
|
private sealed class FakeCurrentUser : ICurrentUser
|
|
{
|
|
public Guid UserId { get; set; }
|
|
public bool IsAuthenticated => UserId != Guid.Empty;
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SearchService_does_not_clamp_large_internal_page()
|
|
{
|
|
var user = Guid.NewGuid();
|
|
var opts = new DbContextOptionsBuilder<AppDbContext>()
|
|
.UseInMemoryDatabase(nameof(SearchService_does_not_clamp_large_internal_page)).Options;
|
|
|
|
using (var seed = new AppDbContext(opts, new FakeCurrentUser()))
|
|
{
|
|
var sender = new Sender { UserId = user, Address = "sender@example.com", DisplayName = "Sender" };
|
|
seed.Senders.Add(sender);
|
|
for (var i = 0; i < 250; i++)
|
|
seed.Emails.Add(new Email { UserId = user, GmailMessageId = $"m{i}", Subject = $"s{i}", SenderId = sender.Id, Sender = sender });
|
|
await seed.SaveChangesAsync();
|
|
}
|
|
|
|
using var ctx = new AppDbContext(opts, new FakeCurrentUser { UserId = user });
|
|
// Mirrors how CleanupService resolves targets: a large page via the parser.
|
|
var request = GmailQueryParser.Parse(null, page: 1, pageSize: 10_000);
|
|
var result = await new SearchService(ctx).SearchAsync(user, request);
|
|
|
|
result.Items.Should().HaveCount(250); // not capped at 200
|
|
result.TotalCount.Should().Be(250);
|
|
}
|
|
}
|