Initial curated itworx-knowledge-hub source from 3b1751d0d9dee551aa6994eee0093704dc334817
This commit is contained in:
commit
ad8ad252a9
825 files changed
+124216
No files matched your search
@@ -0,0 +1,85 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using ItWorx.KnowledgeHub.Contracts.Knowledge;
|
||||
using ItWorx.KnowledgeHub.IntegrationTests.Infrastructure;
|
||||
|
||||
namespace ItWorx.KnowledgeHub.IntegrationTests.Knowledge;
|
||||
|
||||
/// <summary>Concurrent edits must produce an explicit conflict, never a silently lost
|
||||
/// update (docs/27-acceptance-matrix.md, M2-A05).</summary>
|
||||
[Collection(PostgresTestGroup.Name)]
|
||||
public sealed class PageConcurrencyTests(PostgresContainerFixture postgres) : OwnerSetupTestBase(postgres)
|
||||
{
|
||||
[Fact]
|
||||
public async Task Publishing_with_a_stale_ETag_is_rejected_and_never_overwrites_the_newer_revision()
|
||||
{
|
||||
using var client = Factory.CreateClient();
|
||||
await SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword);
|
||||
|
||||
var createResponse = await SetupHelper.PostWithCsrfAsync(
|
||||
client, "/api/v1/pages", new CreatePageRequest(null, "Conflicttest", "WikiPage", "Origineel", null));
|
||||
var page = await createResponse.Content.ReadFromJsonAsync<PageDetail>();
|
||||
Assert.NotNull(page);
|
||||
var staleETag = page.ETag;
|
||||
|
||||
// Session A publishes successfully first, advancing the ETag.
|
||||
var firstEdit = await SetupHelper.PutWithCsrfAsync(
|
||||
client,
|
||||
$"/api/v1/pages/{page.Id}",
|
||||
new UpdatePageRequest("Titel van A", "Inhoud van A", "WikiPage", "Active", null),
|
||||
staleETag);
|
||||
Assert.Equal(HttpStatusCode.OK, firstEdit.StatusCode);
|
||||
|
||||
// Session B still holds the original (now stale) ETag from before A's publish.
|
||||
var secondEdit = await SetupHelper.PutWithCsrfAsync(
|
||||
client,
|
||||
$"/api/v1/pages/{page.Id}",
|
||||
new UpdatePageRequest("Titel van B", "Inhoud van B", "WikiPage", "Active", null),
|
||||
staleETag);
|
||||
|
||||
Assert.Equal(HttpStatusCode.PreconditionFailed, secondEdit.StatusCode);
|
||||
|
||||
// The current server state is still A's edit, not silently overwritten by B.
|
||||
var current = await client.GetFromJsonAsync<PageDetail>($"/api/v1/pages/{page.Id}");
|
||||
Assert.NotNull(current);
|
||||
Assert.Equal("Titel van A", current.Title);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Simultaneous_publishes_return_one_success_and_only_explicit_conflicts()
|
||||
{
|
||||
using var creator = Factory.CreateClient();
|
||||
await SetupHelper.LoginAsync(creator, OwnerEmail, OwnerPassword);
|
||||
|
||||
var createResponse = await SetupHelper.PostWithCsrfAsync(
|
||||
creator, "/api/v1/pages", new CreatePageRequest(null, "Gelijktijdige conflicttest", "WikiPage", "Origineel", null));
|
||||
var page = await createResponse.Content.ReadFromJsonAsync<PageDetail>();
|
||||
Assert.NotNull(page);
|
||||
|
||||
var clients = Enumerable.Range(0, 8).Select(_ => Factory.CreateClient()).ToArray();
|
||||
try
|
||||
{
|
||||
await Task.WhenAll(clients.Select(client => SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword)));
|
||||
var responses = await Task.WhenAll(clients.Select((client, index) =>
|
||||
SetupHelper.PutWithCsrfAsync(
|
||||
client,
|
||||
$"/api/v1/pages/{page.Id}",
|
||||
new UpdatePageRequest($"Kandidaat {index}", $"Inhoud {index}", "WikiPage", "Active", null),
|
||||
page.ETag)));
|
||||
|
||||
Assert.Single(responses, response => response.StatusCode == HttpStatusCode.OK);
|
||||
Assert.Equal(7, responses.Count(response => response.StatusCode == HttpStatusCode.PreconditionFailed));
|
||||
}
|
||||
finally
|
||||
{
|
||||
foreach (var client in clients)
|
||||
{
|
||||
client.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
var revisions = await creator.GetFromJsonAsync<IReadOnlyList<PageRevisionSummary>>($"/api/v1/pages/{page.Id}/revisions");
|
||||
Assert.NotNull(revisions);
|
||||
Assert.Equal(2, revisions.Count);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user