using System.Net.Http.Json; using ItWorx.KnowledgeHub.Contracts.Knowledge; using ItWorx.KnowledgeHub.IntegrationTests.Infrastructure; namespace ItWorx.KnowledgeHub.IntegrationTests.Knowledge; /// Internal links resolve by stable page identity and survive title, slug and /// tree changes (docs/27-acceptance-matrix.md, M2-A02). [Collection(PostgresTestGroup.Name)] public sealed class InternalLinkResolutionTests(PostgresContainerFixture postgres) : OwnerSetupTestBase(postgres) { [Fact] public async Task A_resolved_internal_link_keeps_working_after_the_target_is_renamed_and_moved() { using var client = Factory.CreateClient(); await SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword); var target = await CreatePageAsync(client, null, "PostgreSQL back-up herstellen"); var newParent = await CreatePageAsync(client, null, "Nieuwe categorie"); var sourceResponse = await SetupHelper.PostWithCsrfAsync( client, "/api/v1/pages", new CreatePageRequest( null, "Runbook overzicht", "WikiPage", "Zie [[PostgreSQL back-up herstellen|het herstelrunbook]] voor details.", null)); var source = await sourceResponse.Content.ReadFromJsonAsync(); Assert.NotNull(source); Assert.Contains($"href=\"/knowledge/{target.Id}/{target.Slug}\"", source.RenderedHtml, StringComparison.Ordinal); Assert.Contains("het herstelrunbook", source.RenderedHtml, StringComparison.Ordinal); // Rename AND move the target page. var renamed = await SetupHelper.PutWithCsrfAsync( client, $"/api/v1/pages/{target.Id}", new UpdatePageRequest("PostgreSQL herstelprocedure (bijgewerkt)", target.Markdown, "Runbook", "Active", null), target.ETag); renamed.EnsureSuccessStatusCode(); var renamedTarget = await renamed.Content.ReadFromJsonAsync(); Assert.NotNull(renamedTarget); var moveResponse = await SetupHelper.PostWithCsrfAndIfMatchAsync( client, $"/api/v1/pages/{target.Id}/move", new MovePageRequest(newParent.Id, null), renamedTarget.ETag); moveResponse.EnsureSuccessStatusCode(); // Re-fetch the SOURCE page: its stored link must still resolve to the same // page ID even though the source page's own Markdown/authored link text was // never touched, and the target's title/slug/parent all changed. var sourceAfter = await client.GetFromJsonAsync($"/api/v1/pages/{source.Id}"); Assert.NotNull(sourceAfter); Assert.Contains($"/knowledge/{target.Id}/", sourceAfter.RenderedHtml, StringComparison.Ordinal); Assert.DoesNotContain("[[PostgreSQL", sourceAfter.RenderedHtml, StringComparison.Ordinal); } [Fact] public async Task An_unresolved_internal_link_target_remains_visible_as_plain_bracket_text() { using var client = Factory.CreateClient(); await SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword); var response = await SetupHelper.PostWithCsrfAsync( client, "/api/v1/pages", new CreatePageRequest(null, "Verwijzing naar niets", "WikiPage", "Zie [[Pagina die niet bestaat]].", null)); var page = await response.Content.ReadFromJsonAsync(); Assert.NotNull(page); Assert.Contains("[[Pagina die niet bestaat]]", page.RenderedHtml, StringComparison.Ordinal); Assert.DoesNotContain("(); Assert.NotNull(page); Assert.Contains("[[Overzicht]]", page.RenderedHtml, StringComparison.Ordinal); Assert.DoesNotContain(" CreatePageAsync(HttpClient client, Guid? parentId, string title) { var response = await SetupHelper.PostWithCsrfAsync( client, "/api/v1/pages", new CreatePageRequest(parentId, title, "WikiPage", $"# {title}", null)); response.EnsureSuccessStatusCode(); var page = await response.Content.ReadFromJsonAsync(); Assert.NotNull(page); return page; } }