Files
Inboxintel/tests/InboxIntel.IntegrationTests/SearchRankingTests.cs
T
cesnimda a9be48daee
CI / backend (push) Successful in 1m13s
CI / frontend (push) Successful in 27s
Deploy Staging / deploy (push) Successful in 44s
CI / backend (pull_request) Successful in 1m12s
CI / frontend (pull_request) Successful in 21s
Security / secrets (push) Successful in 5s
Security / dependencies (push) Successful in 1m3s
Security / secrets (pull_request) Successful in 4s
Security / dependencies (pull_request) Successful in 1m3s
feat(search): relevance ranking + websearch_to_tsquery (#13)
2026-07-01 21:08:53 +02:00

54 lines
2.5 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>
/// SearchService now relevance-ranks (ts_rank_cd via RankCoverDensity) when a free-text
/// query is present, falling back to date order otherwise. The ranked path requires a
/// live PostgreSQL instance to exercise (EF's InMemory provider cannot translate
/// websearch_to_tsquery/RankCoverDensity) — this project deliberately avoids a hard
/// Postgres/Testcontainers dependency for tests (see AuthEndpointsTests.TestAppFactory),
/// so ranking correctness itself is verified manually/in staging, not here. What IS
/// covered: the date-order fallback, which is plain LINQ and must not regress.
/// </summary>
public class SearchRankingTests
{
private sealed class FakeCurrentUser : ICurrentUser
{
public Guid UserId { get; set; }
public bool IsAuthenticated => UserId != Guid.Empty;
}
[Fact]
public async Task No_query_falls_back_to_date_descending_order()
{
var user = Guid.NewGuid();
var opts = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase(nameof(No_query_falls_back_to_date_descending_order)).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);
var baseline = DateTimeOffset.UtcNow;
seed.Emails.Add(new Email { UserId = user, GmailMessageId = "oldest", SentAtUtc = baseline.AddDays(-2), Sender = sender });
seed.Emails.Add(new Email { UserId = user, GmailMessageId = "newest", SentAtUtc = baseline, Sender = sender });
seed.Emails.Add(new Email { UserId = user, GmailMessageId = "middle", SentAtUtc = baseline.AddDays(-1), Sender = sender });
await seed.SaveChangesAsync();
}
using var ctx = new AppDbContext(opts, new FakeCurrentUser { UserId = user });
var request = GmailQueryParser.Parse(null, page: 1, pageSize: 50);
var result = await new SearchService(ctx).SearchAsync(user, request);
result.Items.Select(i => i.GmailMessageId).Should().ContainInOrder("newest", "middle", "oldest");
}
}