using Big.Application; using Big.Domain; using Big.Infrastructure; namespace Acceptance.Support; /// An in-memory Workflow Client for the domain acceptance scenario: it records which /// registration a process was started for and returns a fixed instance id. The acceptance test /// drives the use case without a running Flowable (live verification is the verify-domain check). public sealed class InMemoryWorkflowClient : IWorkflowClient { public const string StartedProcessInstanceId = "proc-acc-1"; public RegistrationId? StartedFor { get; private set; } public string? WithdrawnProcessInstanceId { get; private set; } public Task StartRegistrationProcessAsync(RegistrationId registrationId, CancellationToken ct = default) { StartedFor = registrationId; return Task.FromResult(StartedProcessInstanceId); } public Task WithdrawProcessAsync(string processInstanceId, CancellationToken ct = default) { WithdrawnProcessInstanceId = processInstanceId; return Task.CompletedTask; } } /// An in-memory ACL stand-in: records the bsn it opened a zaak for and returns a fixed URL, /// so the scenario verifies the domain crosses the ACL boundary (§8.1) without a running OpenZaak. public sealed class InMemoryAclClient : IAclClient { public static readonly Uri OpenedZaakUrl = new("http://openzaak/zaken/api/v1/zaken/acc-zaak"); public string? OpenedForBsn { get; private set; } public string? OpenedWithReference { get; private set; } public Uri? ApprovedZaakUrl { get; private set; } public Task OpenZaakAsync(string bsn, string reference, CancellationToken ct = default) { OpenedForBsn = bsn; OpenedWithReference = reference; return Task.FromResult(OpenedZaakUrl); } public Task ApproveZaakAsync(Uri zaakUrl, CancellationToken ct = default) { ApprovedZaakUrl = zaakUrl; return Task.CompletedTask; } } /// An in-memory user-task client for the beoordeling acceptance scenario: it holds one open /// Beoordelen task per registration and records the besluit each is completed with. public sealed class InMemoryUserTaskClient : IUserTaskClient { private readonly List _open = []; public (string TaskId, BeoordelingsBesluit Besluit)? Completed { get; private set; } public void Open(RegistrationId registrationId) => _open.Add(new BeoordelingTask($"task-{registrationId}", registrationId)); public Task> GetOpenBeoordelingenAsync(CancellationToken ct = default) => Task.FromResult>(_open); public Task ClaimAsync(string taskId, string behandelaar, CancellationToken ct = default) => Task.CompletedTask; public Task CompleteBeoordelingAsync(string taskId, BeoordelingsBesluit besluit, CancellationToken ct = default) { Completed = (taskId, besluit); _open.RemoveAll(t => t.TaskId == taskId); return Task.CompletedTask; } } /// An in-memory Flowable stand-in for the beoordeling-escalation scenario (S-14): it models /// one Beoordelen task per process instance — its candidate group and whether it is still open — and /// the escalation jobs the non-interrupting 14-day boundary timer parks. It drives the escalation /// worker's behaviour without a running Flowable; the timer firing live is the verify-domain check. public sealed class InMemoryEscalatieClient : IBeoordelingEscalatieClient { private sealed class ParkedTask { public string CandidateGroup { get; set; } = "behandelaar"; public bool IsOpen { get; set; } = true; } private readonly Dictionary _tasks = []; private readonly List _parked = []; private int _seq; /// A registration parks at Beoordelen, claimable by the behandelaar group. public string ParkBeoordeling() { var pid = $"pi-{++_seq}"; _tasks[pid] = new ParkedTask(); return pid; } /// The behandelaar completes the beoordeling before the timer fires: the task closes. public void CompleteBeoordeling(string processInstanceId) => _tasks[processInstanceId].IsOpen = false; /// The 14-day boundary timer fires: a non-interrupting token parks an escalation job. public void FireEscalationTimer(string processInstanceId) => _parked.Add(new EscalatieJob($"job-{++_seq}", processInstanceId)); /// The candidate group that may currently pick the task up. public string CandidateGroupFor(string processInstanceId) => _tasks[processInstanceId].CandidateGroup; public Task> AcquireBeoordelingEscalatieJobsAsync(int maxJobs, CancellationToken ct = default) => Task.FromResult>(_parked.Take(maxJobs).ToList()); public Task ReassignBeoordelingToTeamleadAsync(string processInstanceId, CancellationToken ct = default) { // No-op if the behandelaar already completed it — the timer/completion race (§8.6). var task = _tasks[processInstanceId]; if (task.IsOpen) task.CandidateGroup = "teamlead"; return Task.CompletedTask; } public Task CompleteBeoordelingEscalatieJobAsync(string jobId, CancellationToken ct = default) { _parked.RemoveAll(j => j.JobId == jobId); return Task.CompletedTask; } } /// An in-memory registration store for the domain acceptance scenario. public sealed class InMemoryRegistrationStore : IRegistrationStore { private readonly Dictionary _byId = []; public Task SaveAsync(Registration registration, CancellationToken ct = default) { _byId[registration.Id] = registration; return Task.CompletedTask; } public Task GetAsync(RegistrationId id, CancellationToken ct = default) => Task.FromResult(_byId.GetValueOrDefault(id)); }