Initial curated itworx-knowledge-hub source from 3b1751d0d9dee551aa6994eee0093704dc334817

This commit is contained in:
Public Source Publisher committed 2026-10-01 01:37:37 +02:00
commit ad8ad252a9
825 files changed
+124216

No files matched your search

@@ -0,0 +1,101 @@
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.Extensions.DependencyInjection;
namespace ItWorx.KnowledgeHub.IntegrationTests.Knowledge;
/// <summary>A 10,000-node synthetic hierarchy remains navigable without loading the
/// entire tree into the browser (docs/27-acceptance-matrix.md, M2-A07): the API must
/// serve one bounded page of children quickly, regardless of how many siblings exist
/// in total.</summary>
[Collection(PostgresTestGroup.Name)]
public sealed class PageTreeScaleTests(PostgresContainerFixture postgres) : OwnerSetupTestBase(postgres)
{
private const int ChildCount = 10_000;
[Fact]
public async Task Fetching_one_page_of_children_under_a_10000_child_node_stays_fast_and_bounded()
{
using var client = Factory.CreateClient();
await SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword);
var createResponse = await SetupHelper.PostWithCsrfAsync(
client, "/api/v1/pages", new CreatePageRequest(null, "Grote categorie", "WikiPage", "Root", null));
var parent = await createResponse.Content.ReadFromJsonAsync<PageDetail>();
Assert.NotNull(parent);
await SeedChildrenDirectlyAsync(parent.Id);
var stopwatch = Stopwatch.StartNew();
var firstPage = await client.GetFromJsonAsync<PageChildrenResponse>(
$"/api/v1/pages/{parent.Id}/children?limit=50");
stopwatch.Stop();
Assert.NotNull(firstPage);
Assert.Equal(50, firstPage.Items.Count);
Assert.False(string.IsNullOrEmpty(firstPage.NextCursor));
// Generous bound for a CI/dev machine: the point is "bounded and fast", not a
// precise performance benchmark (that belongs to the M8 release scale suite).
Assert.True(
stopwatch.Elapsed < TimeSpan.FromSeconds(5),
$"Fetching one bounded page of children took {stopwatch.Elapsed}, expected it to stay well under 5s regardless of the {ChildCount} total children.");
var secondPage = await client.GetFromJsonAsync<PageChildrenResponse>(
$"/api/v1/pages/{parent.Id}/children?limit=50&cursor={Uri.EscapeDataString(firstPage.NextCursor!)}");
Assert.NotNull(secondPage);
Assert.Equal(50, secondPage.Items.Count);
Assert.Empty(firstPage.Items.Select(item => item.Id).Intersect(secondPage.Items.Select(item => item.Id)));
}
private async Task SeedChildrenDirectlyAsync(Guid parentId)
{
// Seeded directly through the DbContext rather than 10,000 HTTP calls — this
// test is about proving the *read* path stays bounded at scale, not about
// exercising page creation (already covered by PageCrudTests).
using var scope = Factory.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<KnowledgeHubDbContext>();
var parent = await db.Pages.FindAsync(parentId);
Assert.NotNull(parent);
var workspaceId = parent.WorkspaceId;
var actorId = parent.CreatedBy ?? Guid.NewGuid();
var now = DateTimeOffset.UtcNow;
var sortKey = SortKey.First();
var batch = new List<Page>(1000);
for (var i = 0; i < ChildCount; i++)
{
var page = Page.Create(
workspaceId,
parentId,
sortKey,
PageTitle.Create($"Kindpagina {i:D5}"),
DocumentType.WikiPage,
"Inhoud",
actorId,
now);
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();
}
}
}