using System.Net; using System.Net.Http.Json; using ItWorx.KnowledgeHub.Contracts.Knowledge; using ItWorx.KnowledgeHub.IntegrationTests.Infrastructure; namespace ItWorx.KnowledgeHub.IntegrationTests.Knowledge; /// Every published mutation creates a revision atomically (M3-A01), and a user /// can compare/restore past revisions without losing the current version (M3-A02). [Collection(PostgresTestGroup.Name)] public sealed class RevisionsTests(PostgresContainerFixture postgres) : OwnerSetupTestBase(postgres) { [Fact] public async Task Publishing_creates_a_new_revision_every_time() { using var client = Factory.CreateClient(); await SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword); var createResponse = await SetupHelper.PostWithCsrfAsync( client, "/api/v1/pages", new CreatePageRequest(null, "Revisiepagina", "WikiPage", "Versie 1", null)); var page = await createResponse.Content.ReadFromJsonAsync(); Assert.NotNull(page); await SetupHelper.PutWithCsrfAsync( client, $"/api/v1/pages/{page.Id}", new UpdatePageRequest("Revisiepagina", "Versie 2", "WikiPage", "Active", "Naar versie 2"), page.ETag); var revisions = await client.GetFromJsonAsync>($"/api/v1/pages/{page.Id}/revisions"); Assert.NotNull(revisions); Assert.Equal(2, revisions.Count); // Most recent first. Assert.Equal(2, revisions[0].RevisionNumber); Assert.Equal(1, revisions[1].RevisionNumber); Assert.Equal("Naar versie 2", revisions[0].ChangeSummary); } [Fact] public async Task Restoring_a_past_revision_creates_a_new_revision_without_losing_history() { using var client = Factory.CreateClient(); await SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword); var createResponse = await SetupHelper.PostWithCsrfAsync( client, "/api/v1/pages", new CreatePageRequest(null, "Titel A", "WikiPage", "Inhoud A", null)); var page = await createResponse.Content.ReadFromJsonAsync(); Assert.NotNull(page); var afterFirstEdit = await SetupHelper.PutWithCsrfAsync( client, $"/api/v1/pages/{page.Id}", new UpdatePageRequest("Titel B", "Inhoud B", "WikiPage", "Active", null), page.ETag); var pageAfterEdit = await afterFirstEdit.Content.ReadFromJsonAsync(); Assert.NotNull(pageAfterEdit); Assert.Equal("Titel B", pageAfterEdit.Title); // Fetch the original revision's content before restoring it. var originalRevision = await client.GetFromJsonAsync($"/api/v1/pages/{page.Id}/revisions/1"); Assert.NotNull(originalRevision); Assert.Equal("Titel A", originalRevision.Title); Assert.Equal("Inhoud A", originalRevision.Markdown); var restoreResponse = await SetupHelper.PostWithCsrfAndIfMatchAsync( client, $"/api/v1/pages/{page.Id}/revisions/1/restore", (object?)null, pageAfterEdit.ETag); Assert.Equal(HttpStatusCode.OK, restoreResponse.StatusCode); var restored = await restoreResponse.Content.ReadFromJsonAsync(); Assert.NotNull(restored); Assert.Equal("Titel A", restored.Title); Assert.Equal("Inhoud A", restored.Markdown); Assert.Equal(3, restored.CurrentRevisionNumber); // History is never rewritten: all three revisions remain, including the // "Titel B" state that briefly existed as the current version. var revisions = await client.GetFromJsonAsync>($"/api/v1/pages/{page.Id}/revisions"); Assert.NotNull(revisions); Assert.Equal(3, revisions.Count); Assert.Contains(revisions, revision => revision.RevisionNumber == 2 && revision.Title == "Titel B"); } [Fact] public async Task Restoring_against_a_stale_etag_is_rejected_as_a_conflict() { using var client = Factory.CreateClient(); await SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword); var createResponse = await SetupHelper.PostWithCsrfAsync( client, "/api/v1/pages", new CreatePageRequest(null, "Titel A", "WikiPage", "Inhoud A", null)); var page = await createResponse.Content.ReadFromJsonAsync(); Assert.NotNull(page); await SetupHelper.PutWithCsrfAsync( client, $"/api/v1/pages/{page.Id}", new UpdatePageRequest("Titel B", "Inhoud B", "WikiPage", "Active", null), page.ETag); // page.ETag is now stale (it was captured before the edit above). var restoreResponse = await SetupHelper.PostWithCsrfAndIfMatchAsync( client, $"/api/v1/pages/{page.Id}/revisions/1/restore", (object?)null, page.ETag); Assert.Equal(HttpStatusCode.PreconditionFailed, restoreResponse.StatusCode); } }