Files
itworx-knowledge-hub-Public/tests/backend/ItWorx.KnowledgeHub.IntegrationTests/Knowledge/PageCrudTests.cs
T

197 lines
8.9 KiB
C#

using System.Net;
using System.Net.Http.Json;
using ItWorx.KnowledgeHub.Contracts.Knowledge;
using ItWorx.KnowledgeHub.IntegrationTests.Infrastructure;
namespace ItWorx.KnowledgeHub.IntegrationTests.Knowledge;
[Collection(PostgresTestGroup.Name)]
public sealed class PageCrudTests(PostgresContainerFixture postgres) : OwnerSetupTestBase(postgres)
{
private async Task<HttpClient> AuthenticatedClientAsync()
{
var client = Factory.CreateClient();
await SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword);
return client;
}
[Fact]
public async Task A_root_page_can_be_created_read_and_appears_among_the_roots()
{
using var client = await AuthenticatedClientAsync();
var createResponse = await SetupHelper.PostWithCsrfAsync(
client, "/api/v1/pages", new CreatePageRequest(null, "Kennisbank startpunt", "WikiPage", "# Welkom", null));
Assert.Equal(HttpStatusCode.OK, createResponse.StatusCode);
var created = await createResponse.Content.ReadFromJsonAsync<PageDetail>();
Assert.NotNull(created);
Assert.Equal("Kennisbank startpunt", created.Title);
Assert.Equal("kennisbank-startpunt", created.Slug);
Assert.False(string.IsNullOrWhiteSpace(created.ETag));
var getResponse = await client.GetAsync($"/api/v1/pages/{created.Id}");
Assert.Equal(HttpStatusCode.OK, getResponse.StatusCode);
var fetched = await getResponse.Content.ReadFromJsonAsync<PageDetail>();
Assert.NotNull(fetched);
// Markdig's advanced extensions add deterministic heading anchor IDs
// (docs/11-markdown-and-linking.md — "Heading IDs are deterministic within a
// rendered revision").
Assert.Equal("<h1 id=\"welkom\">Welkom</h1>\n", fetched.RenderedHtml);
Assert.Single(fetched.Breadcrumb);
var roots = await client.GetFromJsonAsync<PageChildrenResponse>("/api/v1/pages/roots");
Assert.NotNull(roots);
Assert.Contains(roots.Items, item => item.Id == created.Id);
}
[Fact]
public async Task A_child_page_reports_a_breadcrumb_and_makes_its_parent_report_children()
{
using var client = await AuthenticatedClientAsync();
var parent = await CreatePageAsync(client, null, "Ouderpagina");
var child = await CreatePageAsync(client, parent.Id, "Kindpagina");
var childDetail = await client.GetFromJsonAsync<PageDetail>($"/api/v1/pages/{child.Id}");
Assert.NotNull(childDetail);
Assert.Equal(2, childDetail.Breadcrumb.Count);
Assert.Equal(parent.Id, childDetail.Breadcrumb[0].Id);
Assert.Equal(child.Id, childDetail.Breadcrumb[1].Id);
var children = await client.GetFromJsonAsync<PageChildrenResponse>($"/api/v1/pages/{parent.Id}/children");
Assert.NotNull(children);
Assert.Single(children.Items);
Assert.Equal(child.Id, children.Items[0].Id);
}
[Fact]
public async Task Editing_a_page_with_the_current_ETag_succeeds_and_returns_a_new_ETag()
{
using var client = await AuthenticatedClientAsync();
var page = await CreatePageAsync(client, null, "Bewerkbare pagina");
var updateResponse = await SetupHelper.PutWithCsrfAsync(
client,
$"/api/v1/pages/{page.Id}",
new UpdatePageRequest("Bewerkte titel", "Nieuwe inhoud", "WikiPage", "Active", "Testbewerking"),
page.ETag);
Assert.Equal(HttpStatusCode.OK, updateResponse.StatusCode);
var updated = await updateResponse.Content.ReadFromJsonAsync<PageDetail>();
Assert.NotNull(updated);
Assert.Equal("Bewerkte titel", updated.Title);
Assert.NotEqual(page.ETag, updated.ETag);
Assert.Equal(2, updated.CurrentRevisionNumber);
}
[Fact]
public async Task Editing_a_page_without_an_if_match_header_is_rejected()
{
using var client = await AuthenticatedClientAsync();
var page = await CreatePageAsync(client, null, "Zonder If-Match");
var csrf = await SetupHelper.GetCsrfTokenAsync(client);
using var request = new HttpRequestMessage(HttpMethod.Put, $"/api/v1/pages/{page.Id}")
{
Content = JsonContent.Create(new UpdatePageRequest("Titel", "Inhoud", "WikiPage", "Active", null))
};
request.Headers.Add(csrf.HeaderName, csrf.Value);
using var response = await client.SendAsync(request);
Assert.Equal(HttpStatusCode.PreconditionRequired, response.StatusCode);
}
[Fact]
public async Task Trashing_a_page_removes_it_from_the_children_listing_and_restoring_brings_it_back()
{
using var client = await AuthenticatedClientAsync();
var parent = await CreatePageAsync(client, null, "Prullenbaktest ouder");
var page = await CreatePageAsync(client, parent.Id, "Weg te gooien pagina");
var trashResponse = await SetupHelper.PostWithCsrfAndIfMatchAsync(
client, $"/api/v1/pages/{page.Id}/trash", (object?)null, page.ETag);
Assert.Equal(HttpStatusCode.OK, trashResponse.StatusCode);
var childrenAfterTrash = await client.GetFromJsonAsync<PageChildrenResponse>($"/api/v1/pages/{parent.Id}/children");
Assert.NotNull(childrenAfterTrash);
Assert.Empty(childrenAfterTrash.Items);
var trashedDetail = await client.GetFromJsonAsync<PageDetail>($"/api/v1/pages/{page.Id}");
Assert.NotNull(trashedDetail);
Assert.NotNull(trashedDetail.TrashedAt);
var restoreResponse = await SetupHelper.PostWithCsrfAndIfMatchAsync(
client, $"/api/v1/pages/{page.Id}/restore", (object?)null, trashedDetail.ETag);
Assert.Equal(HttpStatusCode.OK, restoreResponse.StatusCode);
var childrenAfterRestore = await client.GetFromJsonAsync<PageChildrenResponse>($"/api/v1/pages/{parent.Id}/children");
Assert.NotNull(childrenAfterRestore);
Assert.Single(childrenAfterRestore.Items);
}
[Fact]
public async Task A_trashed_page_cannot_be_edited_until_restored()
{
using var client = await AuthenticatedClientAsync();
var page = await CreatePageAsync(client, null, "Te bewerken na prullenbak");
await SetupHelper.PostWithCsrfAndIfMatchAsync(client, $"/api/v1/pages/{page.Id}/trash", (object?)null, page.ETag);
var trashedDetail = await client.GetFromJsonAsync<PageDetail>($"/api/v1/pages/{page.Id}");
Assert.NotNull(trashedDetail);
var editResponse = await SetupHelper.PutWithCsrfAsync(
client,
$"/api/v1/pages/{page.Id}",
new UpdatePageRequest("Nieuwe titel", "Nieuwe inhoud", "WikiPage", "Active", null),
trashedDetail.ETag);
Assert.Equal(HttpStatusCode.Conflict, editResponse.StatusCode);
}
[Fact]
public async Task Moving_a_page_under_its_own_descendant_is_rejected_as_a_cycle()
{
using var client = await AuthenticatedClientAsync();
var parent = await CreatePageAsync(client, null, "Cyclustest ouder");
var child = await CreatePageAsync(client, parent.Id, "Cyclustest kind");
var moveResponse = await SetupHelper.PostWithCsrfAndIfMatchAsync(
client, $"/api/v1/pages/{parent.Id}/move", new MovePageRequest(child.Id, null), parent.ETag);
Assert.Equal(HttpStatusCode.Conflict, moveResponse.StatusCode);
}
[Fact]
public async Task Reordering_among_siblings_changes_their_listed_order()
{
using var client = await AuthenticatedClientAsync();
var parent = await CreatePageAsync(client, null, "Volgordetest ouder");
var first = await CreatePageAsync(client, parent.Id, "Eerste");
var second = await CreatePageAsync(client, parent.Id, "Tweede");
var beforeChildren = await client.GetFromJsonAsync<PageChildrenResponse>($"/api/v1/pages/{parent.Id}/children");
Assert.NotNull(beforeChildren);
Assert.Equal([first.Id, second.Id], beforeChildren.Items.Select(item => item.Id).ToArray());
// Move "first" to come after "second".
var moveResponse = await SetupHelper.PostWithCsrfAndIfMatchAsync(
client, $"/api/v1/pages/{first.Id}/move", new MovePageRequest(parent.Id, second.Id), first.ETag);
Assert.Equal(HttpStatusCode.OK, moveResponse.StatusCode);
var afterChildren = await client.GetFromJsonAsync<PageChildrenResponse>($"/api/v1/pages/{parent.Id}/children");
Assert.NotNull(afterChildren);
Assert.Equal([second.Id, first.Id], afterChildren.Items.Select(item => item.Id).ToArray());
}
private static async Task<PageDetail> 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<PageDetail>();
Assert.NotNull(page);
return page;
}
}