using System.Net; using System.Net.Http.Json; using ItWorx.KnowledgeHub.Contracts.Projects; using ItWorx.KnowledgeHub.IntegrationTests.Infrastructure; namespace ItWorx.KnowledgeHub.IntegrationTests.Automation; /// The bounded project context bundle (M6-A04): explicit maximum item /// counts, deterministic ordering, and truncation metadata rather than silently /// dropping items (docs/09-api-contract.md "Project context"). [Collection(PostgresTestGroup.Name)] public sealed class ProjectContextTests(PostgresContainerFixture postgres) : OwnerSetupTestBase(postgres) { [Fact] public async Task Project_context_reports_no_truncation_when_everything_fits_within_the_bounds() { using var client = Factory.CreateClient(); await SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword); var projectResponse = await SetupHelper.PostWithCsrfAsync( client, "/api/v1/projects", new CreateProjectRequest("Contextproject", "S.", "Medium", "Start.", Guid.NewGuid())); var project = await projectResponse.Content.ReadFromJsonAsync(); Assert.NotNull(project); await SetupHelper.PostWithCsrfAsync( client, $"/api/v1/projects/{project.Id}/updates", new CreateProjectUpdateRequest("Progress", "Update 1", "S.", "Reported", null, null, null, null, null, null, null, null, null, null)); var context = await client.GetFromJsonAsync($"/api/v1/projects/{project.Id}/context"); Assert.NotNull(context); Assert.Equal(project.Id, context.Project.Id); Assert.False(context.Truncation.Truncated); Assert.Empty(context.Truncation.OmittedCounts); Assert.Single(context.RecentUpdates); } [Fact] public async Task Project_context_truncates_updates_beyond_the_requested_bound_and_reports_the_omitted_count() { using var client = Factory.CreateClient(); await SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword); var projectResponse = await SetupHelper.PostWithCsrfAsync( client, "/api/v1/projects", new CreateProjectRequest("Afgekapt project", "S.", "Medium", "Start.", Guid.NewGuid())); var project = await projectResponse.Content.ReadFromJsonAsync(); Assert.NotNull(project); for (var i = 0; i < 3; i++) { await SetupHelper.PostWithCsrfAsync( client, $"/api/v1/projects/{project.Id}/updates", new CreateProjectUpdateRequest("Progress", $"Update {i}", "S.", "Reported", null, null, null, null, null, null, null, null, null, null)); } var context = await client.GetFromJsonAsync($"/api/v1/projects/{project.Id}/context?updates=2"); Assert.NotNull(context); Assert.Equal(2, context.RecentUpdates.Count); Assert.True(context.Truncation.Truncated); Assert.Equal(1, context.Truncation.OmittedCounts["updates"]); } [Fact] public async Task Project_context_carries_stable_citations_for_every_included_document() { using var client = Factory.CreateClient(); await SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword); var projectResponse = await SetupHelper.PostWithCsrfAsync( client, "/api/v1/projects", new CreateProjectRequest("Citatieproject", "S.", "Medium", "Start.", Guid.NewGuid())); var project = await projectResponse.Content.ReadFromJsonAsync(); Assert.NotNull(project); var pageResponse = await SetupHelper.PostWithCsrfAsync( client, "/api/v1/pages", new ItWorx.KnowledgeHub.Contracts.Knowledge.CreatePageRequest(null, "Architectuurpagina", "WikiPage", "# A", null)); var page = await pageResponse.Content.ReadFromJsonAsync(); Assert.NotNull(page); await SetupHelper.PostWithCsrfAsync( client, $"/api/v1/projects/{project.Id}/documents", new LinkProjectDocumentRequest(page.Id, "Architecture")); var context = await client.GetFromJsonAsync($"/api/v1/projects/{project.Id}/context"); Assert.NotNull(context); var citation = Assert.Single(context.Citations, item => item.Id == page.Id); Assert.Equal("page", citation.Kind); Assert.Equal(page.Title, citation.Title); } [Fact] public async Task Project_context_is_not_reachable_without_authentication() { using var client = Factory.CreateClient(); var response = await client.GetAsync($"/api/v1/projects/{Guid.NewGuid()}/context"); Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); } }