using System.Diagnostics; using System.Net.Http.Json; using ItWorx.KnowledgeHub.Contracts.Knowledge; using ItWorx.KnowledgeHub.Domain.Knowledge; using ItWorx.KnowledgeHub.Infrastructure.Persistence; using ItWorx.KnowledgeHub.IntegrationTests.Infrastructure; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; namespace ItWorx.KnowledgeHub.IntegrationTests.Knowledge; /// /// M3-A07 proof at a reduced, locally-runnable scale. docs/10-search-and-indexing.md's /// aspirational scale-verification dataset (100,000 current pages, 1,000,000 revisions) /// requires dedicated benchmarking infrastructure and multi-minute seeding that does not /// fit a routine local/CI test run; that full-scale verification is explicitly deferred /// to M8's "Performance and scale evidence" gate (docs/adr/ADR-012-m3-discovery-revisions-search.md). /// This test instead proves the search query plan and GIN indexes keep a bounded, /// permission-filtered query fast and correct at a smaller but still non-trivial /// synthetic scale — the same proof shape as M2-A07's 10,000-page tree scale test. /// [Collection(PostgresTestGroup.Name)] public sealed class SearchScaleTests(PostgresContainerFixture postgres) : OwnerSetupTestBase(postgres) { private const int PageCount = 5_000; [Fact] public async Task Search_stays_fast_and_correct_against_5000_pages_with_one_needle() { using var client = Factory.CreateClient(); await SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword); Guid needleId; using (var scope = Factory.Services.CreateScope()) { var db = scope.ServiceProvider.GetRequiredService(); var workspace = await db.Workspaces.AsNoTracking().FirstAsync(); var owner = await db.Users.AsNoTracking().FirstAsync(); var sortKey = SortKey.First(); var batch = new List(1000); Page? needle = null; for (var i = 0; i < PageCount; i++) { var isNeedle = i == PageCount / 2; var page = Page.Create( workspace.Id, null, sortKey, PageTitle.Create(isNeedle ? "Uniek vindbaar trefwoord xyzzyplugh" : $"Vulpagina {i:D5}"), DocumentType.WikiPage, isNeedle ? "Deze pagina bevat het unieke trefwoord xyzzyplugh nergens anders." : "Gewone opvultekst zonder bijzondere termen.", owner.Id, DateTimeOffset.UtcNow); if (isNeedle) { needle = page; } batch.Add(page); sortKey = SortKey.Next(sortKey); if (batch.Count == 1000) { db.Pages.AddRange(batch); await db.SaveChangesAsync(); db.ChangeTracker.Clear(); batch.Clear(); } } if (batch.Count > 0) { db.Pages.AddRange(batch); await db.SaveChangesAsync(); } Assert.NotNull(needle); needleId = needle.Id; } var stopwatch = Stopwatch.StartNew(); var response = await client.GetFromJsonAsync("/api/v1/search?q=xyzzyplugh"); stopwatch.Stop(); Assert.NotNull(response); Assert.Contains(response.Items, item => item.Id == needleId); // Generous bound for a CI/dev machine, proving the GIN-indexed query stays // bounded rather than scanning ~5,000 rows' worth of text at request time — not // a precise benchmark (that belongs to the M8 release scale suite at full // documented scale). Assert.True( stopwatch.Elapsed < TimeSpan.FromSeconds(3), $"Searching {PageCount} pages for one needle term took {stopwatch.Elapsed}, expected well under 3s."); } }