feat(self-service): trek aanvraag in — withdrawal action (S-11c-2, closes #12) (#93)
All checks were successful
All checks were successful
## What & why Final sub-slice of **S-11 · Withdrawal (Flow 3)** — the user-facing "trek aanvraag in" action, which **closes #12**. - **self-service portal**: the submit confirmation gains a **"Trek aanvraag in"** button. It withdraws the just-submitted registration via `postSelfServiceRegistrationsIdWithdraw(reference)`; success shows an *ingetrokken* confirmation, a failure is surfaced (`role="alert"`) and the action stays available — same confirm-and-surface pattern as submit. - **acceptance**: `Een registratie intrekken` — owner withdraws → INGETROKKEN + workflow cancelled; a different bsn is reported not-found. - **e2e**: `withdrawal.spec.ts` — DigiD submit → trek aanvraag in → the portal confirms ingetrokken. - **docs**: demo-script + frontend-decisions. Together with S-11a (#88), S-11b (#89), S-11c-1 (#90), this completes the flow: citizen withdraws → domain INGETROKKEN → BPMN message event cancels the process → the case leaves the behandelaar's werkbak. Closes #12 ## Definition of Done - [x] Linked Gitea issue (#12). - [x] Failing tests committed before the implementation. - [x] Implementation makes the tests pass. - [x] Conventional Commits referencing the issue (`refs #12`). - [ ] CI green — all Gitea Actions jobs. - [x] `docker compose up` unaffected. - [x] Docs updated (demo-script + frontend-decisions). - [x] ADR — ADR-0014 (from S-11b) covers the cancellation decision; nothing new here. ## Notes for reviewers - Full local gate run before pushing: `dotnet format --verify-no-changes` clean; `make unit` green (Acceptance **11** incl. the 2 new withdrawal scenarios, Big 95, BFF 30, Acl 27, EventSubscriber 19); self-service lint/test/build green (9 tests, incl. the 2 new withdraw tests). - `withdrawal.spec.ts` waits on the *ingetrokken* confirmation (which only renders after the withdraw POST returns), so it can't cancel the request early (the 499 lesson from #87). Live-validated by verify-stack. Reviewed-on: #93
This commit was merged in pull request #93.
This commit is contained in:
22
tests/acceptance/Features/EenRegistratieIntrekken.feature
Normal file
22
tests/acceptance/Features/EenRegistratieIntrekken.feature
Normal file
@@ -0,0 +1,22 @@
|
||||
# language: en
|
||||
# Drives S-11 (#12). A zorgprofessional withdraws their own submitted registration ("trek aanvraag
|
||||
# in"): it advances to INGETROKKEN and its running workflow is cancelled (ADR-0014). Only the owner
|
||||
# may withdraw — another bsn is told not-found. Exercised against in-memory stand-ins for the store
|
||||
# and the Workflow Client; the live Flowable message correlation is verified by the domain check.
|
||||
Feature: Een registratie intrekken
|
||||
Als zorgprofessional wil ik mijn ingediende registratie kunnen intrekken
|
||||
zodat een aanvraag die ik niet meer wil niet in behandeling blijft.
|
||||
|
||||
Scenario: De zorgprofessional trekt zijn eigen registratie in
|
||||
Given a submitted registration with a running process
|
||||
When the zorgprofessional withdraws it
|
||||
Then the withdrawal succeeds
|
||||
And the registration has status "INGETROKKEN"
|
||||
And the running process is cancelled
|
||||
|
||||
Scenario: Een andere zorgprofessional kan de registratie niet intrekken
|
||||
Given a submitted registration with a running process
|
||||
When a different zorgprofessional tries to withdraw it
|
||||
Then the withdrawal is reported not found
|
||||
And the registration has status "INGEDIEND"
|
||||
And the running process is not cancelled
|
||||
59
tests/acceptance/Steps/EenRegistratieIntrekkenSteps.cs
Normal file
59
tests/acceptance/Steps/EenRegistratieIntrekkenSteps.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using Acceptance.Support;
|
||||
using Big.Application;
|
||||
using Big.Domain;
|
||||
using Reqnroll;
|
||||
using Xunit;
|
||||
|
||||
namespace Acceptance.Steps;
|
||||
|
||||
/// <summary>Bindings for <c>EenRegistratieIntrekken.feature</c> (S-11). Submits a registration (which
|
||||
/// records its process) and then applies the WithdrawRegistration use case against in-memory ports;
|
||||
/// one instance per scenario. Scoped to this feature so its "the registration has status" step does
|
||||
/// not clash with the identically phrased steps in the other features.</summary>
|
||||
[Binding]
|
||||
[Scope(Feature = "Een registratie intrekken")]
|
||||
public sealed class EenRegistratieIntrekkenSteps
|
||||
{
|
||||
private const string OwnerBsn = "123456782";
|
||||
|
||||
private readonly InMemoryRegistrationStore _store = new();
|
||||
private readonly InMemoryWorkflowClient _workflow = new();
|
||||
private RegistrationId _id;
|
||||
private WithdrawOutcome _outcome;
|
||||
|
||||
[Given("a submitted registration with a running process")]
|
||||
public async Task GivenASubmittedRegistrationWithARunningProcess()
|
||||
=> _id = await new SubmitRegistration(_store, _workflow).HandleAsync(new SubmitRegistrationCommand(OwnerBsn));
|
||||
|
||||
[When("the zorgprofessional withdraws it")]
|
||||
public async Task WhenTheZorgprofessionalWithdrawsIt()
|
||||
=> _outcome = await new WithdrawRegistration(_store, _workflow).HandleAsync(
|
||||
new WithdrawRegistrationCommand(_id, OwnerBsn));
|
||||
|
||||
[When("a different zorgprofessional tries to withdraw it")]
|
||||
public async Task WhenADifferentZorgprofessionalTriesToWithdrawIt()
|
||||
=> _outcome = await new WithdrawRegistration(_store, _workflow).HandleAsync(
|
||||
new WithdrawRegistrationCommand(_id, "999999990"));
|
||||
|
||||
[Then("the withdrawal succeeds")]
|
||||
public void ThenTheWithdrawalSucceeds() => Assert.Equal(WithdrawOutcome.Withdrawn, _outcome);
|
||||
|
||||
[Then("the withdrawal is reported not found")]
|
||||
public void ThenTheWithdrawalIsReportedNotFound() => Assert.Equal(WithdrawOutcome.NotFound, _outcome);
|
||||
|
||||
[Then("the registration has status \"(.*)\"")]
|
||||
public async Task ThenTheRegistrationHasStatus(string expected)
|
||||
{
|
||||
var registration = await _store.GetAsync(_id);
|
||||
Assert.NotNull(registration);
|
||||
Assert.Equal(expected, registration.Status.ToString().ToUpperInvariant());
|
||||
}
|
||||
|
||||
[Then("the running process is cancelled")]
|
||||
public void ThenTheRunningProcessIsCancelled()
|
||||
=> Assert.Equal(InMemoryWorkflowClient.StartedProcessInstanceId, _workflow.WithdrawnProcessInstanceId);
|
||||
|
||||
[Then("the running process is not cancelled")]
|
||||
public void ThenTheRunningProcessIsNotCancelled()
|
||||
=> Assert.Null(_workflow.WithdrawnProcessInstanceId);
|
||||
}
|
||||
27
tests/e2e/withdrawal.spec.ts
Normal file
27
tests/e2e/withdrawal.spec.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
// S-11 (Flow 3): a zorgprofessional logs in via mock DigiD, submits a registration, then withdraws
|
||||
// it ("trek aanvraag in") from the self-service portal. The withdrawal goes portal → BFF (owner-
|
||||
// scoped by the DigiD token's bsn) → domain, which cancels the running workflow (ADR-0014); the page
|
||||
// then confirms the registration is ingetrokken.
|
||||
test('DigiD submit → trek aanvraag in → self-service confirms ingetrokken', async ({ page }) => {
|
||||
// Visiting the guarded page redirects to the Keycloak (mock DigiD) login.
|
||||
await page.goto('/');
|
||||
|
||||
await page.locator('#username').fill('jan-burger');
|
||||
await page.locator('#password').fill('test123');
|
||||
await page.locator('#kc-login').click();
|
||||
|
||||
await expect(page.getByRole('heading', { name: /Zelfservice/i })).toBeVisible();
|
||||
|
||||
await page.getByRole('button', { name: /indienen/i }).click();
|
||||
|
||||
// The BFF accepted it and the page shows the confirmation with the reference.
|
||||
await expect(page.getByText(/ontvangen/i)).toBeVisible();
|
||||
|
||||
// Withdraw it. The confirmation of withdrawal appears only after the decide POST completes (204),
|
||||
// so awaiting the "ingetrokken" text also proves the request landed — no premature navigation.
|
||||
await page.getByRole('button', { name: /trek aanvraag in/i }).click();
|
||||
|
||||
await expect(page.getByText(/is ingetrokken/i)).toBeVisible();
|
||||
});
|
||||
Reference in New Issue
Block a user