using System.Net; using System.Net.Http.Json; using ItWorx.KnowledgeHub.Contracts.Knowledge; using ItWorx.KnowledgeHub.IntegrationTests.Infrastructure; namespace ItWorx.KnowledgeHub.IntegrationTests.Knowledge; /// Autosaved drafts survive navigation/restart without overwriting a newer /// server revision (docs/27-acceptance-matrix.md, M2-A04). [Collection(PostgresTestGroup.Name)] public sealed class DraftAutosaveTests(PostgresContainerFixture postgres) : OwnerSetupTestBase(postgres) { [Fact] public async Task A_saved_draft_can_be_retrieved_later_as_if_after_a_browser_restart() { using var writer = Factory.CreateClient(); await SetupHelper.LoginAsync(writer, OwnerEmail, OwnerPassword); var draftKey = $"new-page-{Guid.NewGuid():N}"; var saveResponse = await SetupHelper.PutWithCsrfAsync( writer, $"/api/v1/drafts/{draftKey}", new SaveDraftRequest(null, null, "Concepttitel", "Conceptinhoud")); Assert.Equal(HttpStatusCode.OK, saveResponse.StatusCode); // A brand new HttpClient with its own (empty) cookie jar, logging in again — // simulating the browser having fully restarted. using var afterRestart = Factory.CreateClient(); await SetupHelper.LoginAsync(afterRestart, OwnerEmail, OwnerPassword); var fetched = await afterRestart.GetFromJsonAsync($"/api/v1/drafts/{draftKey}"); Assert.NotNull(fetched); Assert.Equal("Concepttitel", fetched.Title); Assert.Equal("Conceptinhoud", fetched.Markdown); } [Fact] public async Task Saving_a_draft_against_an_outdated_base_revision_is_flagged_as_stale_not_silently_accepted() { using var client = Factory.CreateClient(); await SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword); var createResponse = await SetupHelper.PostWithCsrfAsync( client, "/api/v1/pages", new CreatePageRequest(null, "Concepttest pagina", "WikiPage", "Origineel", null)); var page = await createResponse.Content.ReadFromJsonAsync(); Assert.NotNull(page); // A draft is started against revision 1... var draftKey = page.Id.ToString(); await SetupHelper.PutWithCsrfAsync( client, $"/api/v1/drafts/{draftKey}", new SaveDraftRequest(page.Id, page.CurrentRevisionNumber, page.Title, "Conceptwijziging")); // ...but someone else (or another tab) publishes revision 2 in the meantime. await SetupHelper.PutWithCsrfAsync( client, $"/api/v1/pages/{page.Id}", new UpdatePageRequest("Gepubliceerde titel", "Gepubliceerde inhoud", "WikiPage", "Active", null), page.ETag); // Saving the draft again must surface that its base revision is now stale, // rather than silently proceeding as if nothing happened. var staleSave = await SetupHelper.PutWithCsrfAsync( client, $"/api/v1/drafts/{draftKey}", new SaveDraftRequest(page.Id, page.CurrentRevisionNumber, page.Title, "Nog een conceptwijziging")); var staleDraft = await staleSave.Content.ReadFromJsonAsync(); Assert.NotNull(staleDraft); Assert.True(staleDraft.BaseRevisionIsStale); // Crucially, the published page itself was never overwritten by the draft. var currentPage = await client.GetFromJsonAsync($"/api/v1/pages/{page.Id}"); Assert.NotNull(currentPage); Assert.Equal("Gepubliceerde titel", currentPage.Title); } [Fact] public async Task Discarding_a_draft_removes_it() { using var client = Factory.CreateClient(); await SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword); var draftKey = $"discard-me-{Guid.NewGuid():N}"; await SetupHelper.PutWithCsrfAsync( client, $"/api/v1/drafts/{draftKey}", new SaveDraftRequest(null, null, "Weg te gooien", "Inhoud")); var csrf = await SetupHelper.GetCsrfTokenAsync(client); using var request = new HttpRequestMessage(HttpMethod.Delete, $"/api/v1/drafts/{draftKey}"); request.Headers.Add(csrf.HeaderName, csrf.Value); using var deleteResponse = await client.SendAsync(request); Assert.Equal(HttpStatusCode.OK, deleteResponse.StatusCode); var getResponse = await client.GetAsync($"/api/v1/drafts/{draftKey}"); Assert.Equal(HttpStatusCode.NotFound, getResponse.StatusCode); } }