test(bff): self-service documents endpoint forwards id + bsn to the domain (refs #102)
RED: POST /self-service/registrations/{id}/documents requires a digid token, takes
the bsn from the token, forwards to the domain, and relays the domain's 404 for an
unknown/not-owned registration. Adds the IDomainClient.ProvideDocumentsAsync port +
client + fake; the endpoint itself follows.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -27,6 +27,11 @@ public interface IDomainClient
|
|||||||
/// unknown or not the caller's (404), so the BFF can relay a 404 rather than a 500.</summary>
|
/// unknown or not the caller's (404), so the BFF can relay a 404 rather than a 500.</summary>
|
||||||
Task<bool> WithdrawRegistrationAsync(string registrationId, string bsn, CancellationToken ct = default);
|
Task<bool> WithdrawRegistrationAsync(string registrationId, string bsn, CancellationToken ct = default);
|
||||||
|
|
||||||
|
/// <summary>Provide the documents the caller's own registration is waiting for ("documenten
|
||||||
|
/// aanleveren"). Owner-scoped by <paramref name="bsn"/>. Returns <c>false</c> when the domain
|
||||||
|
/// reports the registration is unknown or not the caller's (404), so the BFF can relay a 404.</summary>
|
||||||
|
Task<bool> ProvideDocumentsAsync(string registrationId, string bsn, CancellationToken ct = default);
|
||||||
|
|
||||||
/// <summary>The behandelaar's werkbak — registrations awaiting beoordeling.</summary>
|
/// <summary>The behandelaar's werkbak — registrations awaiting beoordeling.</summary>
|
||||||
Task<IReadOnlyList<WerkbakItem>> GetWerkbakAsync(CancellationToken ct = default);
|
Task<IReadOnlyList<WerkbakItem>> GetWerkbakAsync(CancellationToken ct = default);
|
||||||
|
|
||||||
@@ -63,6 +68,17 @@ public sealed class DomainClient(HttpClient http) : IDomainClient
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ProvideDocumentsAsync(string registrationId, string bsn, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
using var response = await http.PostAsJsonAsync(
|
||||||
|
$"registrations/{registrationId}/documents", new { bsn }, ct);
|
||||||
|
// The domain 404s an unknown or not-owned registration; relay that rather than fail hard.
|
||||||
|
if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
|
||||||
|
return false;
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<IReadOnlyList<WerkbakItem>> GetWerkbakAsync(CancellationToken ct = default)
|
public async Task<IReadOnlyList<WerkbakItem>> GetWerkbakAsync(CancellationToken ct = default)
|
||||||
=> await http.GetFromJsonAsync<List<WerkbakItem>>("behandel/werkbak", ct) ?? [];
|
=> await http.GetFromJsonAsync<List<WerkbakItem>>("behandel/werkbak", ct) ?? [];
|
||||||
|
|
||||||
|
|||||||
@@ -94,6 +94,18 @@ internal sealed class FakeDomainClient : IDomainClient
|
|||||||
return Task.FromResult(WithdrawSucceeds);
|
return Task.FromResult(WithdrawSucceeds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public (string RegistrationId, string Bsn)? DocumentsProvidedFor { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>Whether the fake domain reports the provide-documents as done (true → 204) or
|
||||||
|
/// not-found/not-owned (false → 404). Tests set this to exercise the relay.</summary>
|
||||||
|
public bool ProvideDocumentsSucceeds { get; set; } = true;
|
||||||
|
|
||||||
|
public Task<bool> ProvideDocumentsAsync(string registrationId, string bsn, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
DocumentsProvidedFor = (registrationId, bsn);
|
||||||
|
return Task.FromResult(ProvideDocumentsSucceeds);
|
||||||
|
}
|
||||||
|
|
||||||
public (string RegistrationId, string Besluit)? Decided { get; private set; }
|
public (string RegistrationId, string Besluit)? Decided { get; private set; }
|
||||||
|
|
||||||
public Task<IReadOnlyList<WerkbakItem>> GetWerkbakAsync(CancellationToken ct = default)
|
public Task<IReadOnlyList<WerkbakItem>> GetWerkbakAsync(CancellationToken ct = default)
|
||||||
|
|||||||
@@ -112,5 +112,46 @@ public class SelfServiceEndpointTests
|
|||||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static HttpRequestMessage ProvideDocuments(string? bearer, string id = "reg-123")
|
||||||
|
{
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Post, $"/self-service/registrations/{id}/documents");
|
||||||
|
if (bearer is not null)
|
||||||
|
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", bearer);
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Rejects_providing_documents_without_a_token()
|
||||||
|
{
|
||||||
|
using var factory = new BffFactory();
|
||||||
|
|
||||||
|
var response = await factory.CreateClient().SendAsync(ProvideDocuments(bearer: null));
|
||||||
|
|
||||||
|
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
|
||||||
|
Assert.Null(factory.Domain.DocumentsProvidedFor);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Provides_documents_for_the_callers_registration_forwarding_the_id_and_bsn()
|
||||||
|
{
|
||||||
|
using var factory = new BffFactory();
|
||||||
|
|
||||||
|
var response = await factory.CreateClient().SendAsync(ProvideDocuments(TestTokens.Valid("123456782"), "reg-9"));
|
||||||
|
|
||||||
|
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
|
||||||
|
Assert.Equal(("reg-9", "123456782"), factory.Domain.DocumentsProvidedFor);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Relays_not_found_providing_documents_for_an_unknown_or_not_owned_registration()
|
||||||
|
{
|
||||||
|
using var factory = new BffFactory();
|
||||||
|
factory.Domain.ProvideDocumentsSucceeds = false;
|
||||||
|
|
||||||
|
var response = await factory.CreateClient().SendAsync(ProvideDocuments(TestTokens.Valid("123456782")));
|
||||||
|
|
||||||
|
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
private sealed record SubmitAcceptedDto(string RegistrationId, string Status);
|
private sealed record SubmitAcceptedDto(string RegistrationId, string Status);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user