test(domain): SubmitRegistration + OpenZaakWorker use cases (refs #6)

Failing application-layer tests over fake ports (IWorkflowClient, IAclClient,
IRegistrationStore):
- Submit persists an INGEDIEND registration and starts the registratie process,
  recording the instance id — and persists *before* starting, so the worker can
  correlate the OpenZaakAanmaken job back to its aggregate (ADR-0009).
- The worker opens a zaak via the ACL and attaches it; an unknown registration
  throws (job left for redelivery); a redelivered job is idempotent and opens no
  second zaak (§8.6).

Handlers are stubs (no persistence / no ACL call) so the tests compile and fail
on their assertions; the green commit implements them.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-30 16:59:27 +02:00
parent 53751fd1bc
commit 8d176c2603
10 changed files with 289 additions and 0 deletions

View File

@@ -0,0 +1,47 @@
using Big.Domain;
namespace Big.Application;
/// <summary>
/// The port to the workflow engine. Implemented by the Workflow Client in Infrastructure — the
/// <em>only</em> code that talks to Flowable (CLAUDE.md §8.2). The application asks it to start the
/// registratie process; it never knows Flowable exists.
/// </summary>
public interface IWorkflowClient
{
/// <summary>
/// Start one <c>registratie</c> process instance for the given registration, carrying the
/// registration id so the <c>OpenZaakAanmaken</c> external task can be correlated back to its
/// aggregate. Returns the process instance id.
/// </summary>
Task<string> StartRegistrationProcessAsync(RegistrationId registrationId, CancellationToken ct = default);
}
/// <summary>
/// The port to the Anti-Corruption Layer. Implemented in Infrastructure by an HTTP client to the
/// ACL service — the <em>only</em> code that talks to ZGW (CLAUDE.md §8.1). The domain hands over a
/// bsn; the ACL default-fills the ZGW-mandatory fields (ADR-0003) and returns the created zaak URL.
/// </summary>
public interface IAclClient
{
Task<Uri> OpenZaakAsync(string bsn, CancellationToken ct = default);
}
/// <summary>
/// Persistence port for the <see cref="Registration"/> aggregate. In-memory for the minimal slice
/// (ADR-0009); an EF-backed store is a documented follow-up, and this port keeps that change additive.
/// </summary>
public interface IRegistrationStore
{
/// <summary>Insert or update the registration, keyed on its id.</summary>
Task SaveAsync(Registration registration, CancellationToken ct = default);
/// <summary>Load a registration by id, or <c>null</c> if none exists.</summary>
Task<Registration?> GetAsync(RegistrationId id, CancellationToken ct = default);
}
/// <summary>
/// An acquired <c>OpenZaakAanmaken</c> external-worker job: the Flowable job id (needed to complete
/// it) and the registration id it carries as a process variable.
/// </summary>
public sealed record OpenZaakJob(string JobId, RegistrationId RegistrationId);