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,83 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using ItWorx.KnowledgeHub.Contracts.Projects;
|
||||
using ItWorx.KnowledgeHub.IntegrationTests.Infrastructure;
|
||||
|
||||
namespace ItWorx.KnowledgeHub.IntegrationTests.Automation;
|
||||
|
||||
/// <summary>A retried create/append request carrying the same
|
||||
/// <c>Idempotency-Key</c> must never execute the mutation twice
|
||||
/// (docs/09-api-contract.md).</summary>
|
||||
[Collection(PostgresTestGroup.Name)]
|
||||
public sealed class IdempotencyTests(PostgresContainerFixture postgres) : OwnerSetupTestBase(postgres)
|
||||
{
|
||||
[Fact]
|
||||
public async Task Retrying_a_project_update_with_the_same_Idempotency_Key_does_not_create_a_second_update()
|
||||
{
|
||||
using var client = Factory.CreateClient();
|
||||
await SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword);
|
||||
|
||||
var projectResponse = await SetupHelper.PostWithCsrfAsync(
|
||||
client, "/api/v1/projects",
|
||||
new CreateProjectRequest("Idempotentieproject", "S.", "Medium", "Start.", Guid.NewGuid()));
|
||||
var project = await projectResponse.Content.ReadFromJsonAsync<ProjectDetail>();
|
||||
Assert.NotNull(project);
|
||||
|
||||
var idempotencyKey = Guid.NewGuid().ToString();
|
||||
var request = new CreateProjectUpdateRequest(
|
||||
"Progress", "Idempotente update", "Samenvatting.", "Reported", null, null, null, null, null, null, null, null, null, null);
|
||||
|
||||
var first = await PostWithIdempotencyKeyAsync(client, $"/api/v1/projects/{project.Id}/updates", request, idempotencyKey);
|
||||
Assert.Equal(HttpStatusCode.OK, first.StatusCode);
|
||||
var firstBody = await first.Content.ReadFromJsonAsync<ProjectUpdateSummary>();
|
||||
|
||||
var second = await PostWithIdempotencyKeyAsync(client, $"/api/v1/projects/{project.Id}/updates", request, idempotencyKey);
|
||||
Assert.Equal(HttpStatusCode.OK, second.StatusCode);
|
||||
var secondBody = await second.Content.ReadFromJsonAsync<ProjectUpdateSummary>();
|
||||
|
||||
// The replayed response is the exact cached original — same update ID — not a
|
||||
// freshly created second row.
|
||||
Assert.Equal(firstBody!.Id, secondBody!.Id);
|
||||
|
||||
var timeline = await client.GetFromJsonAsync<ProjectUpdatesEnvelope>($"/api/v1/projects/{project.Id}/updates");
|
||||
Assert.NotNull(timeline);
|
||||
Assert.Single(timeline.Items, update => update.Title == "Idempotente update");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Reusing_an_Idempotency_Key_with_different_request_content_is_a_conflict()
|
||||
{
|
||||
using var client = Factory.CreateClient();
|
||||
await SetupHelper.LoginAsync(client, OwnerEmail, OwnerPassword);
|
||||
|
||||
var projectResponse = await SetupHelper.PostWithCsrfAsync(
|
||||
client, "/api/v1/projects",
|
||||
new CreateProjectRequest("Conflictproject idempotentie", "S.", "Medium", "Start.", Guid.NewGuid()));
|
||||
var project = await projectResponse.Content.ReadFromJsonAsync<ProjectDetail>();
|
||||
Assert.NotNull(project);
|
||||
|
||||
var idempotencyKey = Guid.NewGuid().ToString();
|
||||
var first = new CreateProjectUpdateRequest(
|
||||
"Progress", "Eerste inhoud", "S.", "Reported", null, null, null, null, null, null, null, null, null, null);
|
||||
var different = new CreateProjectUpdateRequest(
|
||||
"Progress", "Andere inhoud", "S.", "Reported", null, null, null, null, null, null, null, null, null, null);
|
||||
|
||||
var firstResponse = await PostWithIdempotencyKeyAsync(client, $"/api/v1/projects/{project.Id}/updates", first, idempotencyKey);
|
||||
Assert.Equal(HttpStatusCode.OK, firstResponse.StatusCode);
|
||||
|
||||
var conflictResponse = await PostWithIdempotencyKeyAsync(client, $"/api/v1/projects/{project.Id}/updates", different, idempotencyKey);
|
||||
Assert.Equal(HttpStatusCode.Conflict, conflictResponse.StatusCode);
|
||||
}
|
||||
|
||||
private static async Task<HttpResponseMessage> PostWithIdempotencyKeyAsync<TBody>(
|
||||
HttpClient client, string path, TBody body, string idempotencyKey)
|
||||
{
|
||||
var csrf = await SetupHelper.GetCsrfTokenAsync(client);
|
||||
using var request = new HttpRequestMessage(HttpMethod.Post, path) { Content = JsonContent.Create(body) };
|
||||
request.Headers.Add(csrf.HeaderName, csrf.Value);
|
||||
request.Headers.Add("Idempotency-Key", idempotencyKey);
|
||||
return await client.SendAsync(request);
|
||||
}
|
||||
|
||||
private sealed record ProjectUpdatesEnvelope(IReadOnlyList<ProjectUpdateSummary> Items, string? NextCursor);
|
||||
}
|
||||
Reference in new issue
Block a user