fix(uploads): delete the dead POST /registrations (RB-06)

POST /registrations passed its Documents list straight to Submit, which calls
DocumentStore.Link on every digital documentId in it — and linking a document
blocks its owner from ever deleting it (DeleteOwned returns 409 Linked). That
path had no ForeignIds ownership check, so any authenticated citizen could
post another citizen's document id and permanently block them from deleting
their own diploma scan. POST /applications/{id}/submit, the endpoint actually
in use, has had that guard since it was written.

Deleted rather than guarded: the endpoint is dead. No frontend caller, and
the whole registratie flow goes through /applications/{id}/submit.
RegistratieRequest went with it, and so did SubmissionRules.RejectRegistratie
— reachable only from here, and contradicted by the live path, which treats a
handmatig diploma as "does not auto-approve" rather than a 422 rejection. Its
own message said as much while being returned as a rejection. That last part
is a judgement call beyond the ticket's wording; reverting the two
SubmissionRules hunks restores it in isolation.

Coverage moved rather than vanished: the problem+json shape assertion is now
on /change-requests (the other endpoint on the same Submit helper), and the
linked-delete 409 test goes through the real submit path.

swagger.json, the generated client and the behaviour spec regenerated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
eho
2026-08-27 11:04:03 +02:00
co-authored by Claude Opus 5
parent 5187bfa19a
commit 0298ecc506
14 changed files with 123 additions and 185 deletions
+13 -6
View File
@@ -21,7 +21,7 @@ tested where._
Every bullet below is a real test name from the suite — an `it()` title (frontend) or a test
method name (backend), read as a sentence. Nothing here is hand-written prose: this page
**is** the suite, reshaped for a business reader. 402 frontend behaviours across
8 contexts; 221 backend behaviours across 37 test
8 contexts; 225 backend behaviours across 38 test
classes.
## Frontend (by context)
@@ -825,6 +825,7 @@ classes.
- A denied admin action is recorded
- A reveal attempt is recorded
- No audit row carries a subjects bsn
- The audit schema carries no pii
### AuthzTests
@@ -921,10 +922,8 @@ classes.
- Brp returns address
- Duo lookup carries server decided questions and professions
- IntakePolicy returns scholing threshold
- Registration with duo diploma succeeds
- Registration with manual diploma is rejected with problem details
- Change request with valid phone succeeds
- Change request with bad phone is rejected
- Change request with bad phone is rejected with problem details
- Health endpoint is ok
- Correlation id supplied by the caller is echoed back
- Correlation id is generated when the caller omits it
@@ -1087,12 +1086,19 @@ classes.
### SubmissionRuleTests
- Manual diploma is rejected
- Duo diploma is accepted
- Zero hours is rejected
- Worked hours are accepted
- Phone change is validated
### UploadAccessTests
- The owner can read the bytes
- Another citizen gets 404 not 403
- A behandelaar can read a linked document
- A medewerker without the behandelaar rol does not
- The document audit trail records a masked actor
- Status reports another citizens localId as unknown
### WerkvoorraadTests
- Behandelaar sees submitted cases in the queue
@@ -1109,6 +1115,7 @@ classes.
- Submit with a failing zgw flags the divergence instead of diverging silently
- Submit with a healthy zgw leaves no divergence flag
- A recorded divergence carries no response body and no query string
### ZgwTokenProviderTests
@@ -359,52 +359,6 @@ export class ApiClient {
return Promise.resolve<StamdataTableDto>(null as any);
}
/**
* @return OK
*/
registrations(body: RegistratieRequest): Promise<ReferentieResponse> {
let url_ = this.baseUrl + "/api/v1/registrations";
url_ = url_.replace(/[?&]$/, "");
const content_ = JSON.stringify(body);
let options_: RequestInit = {
body: content_,
method: "POST",
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
}
};
return this.http.fetch(url_, options_).then((_response: Response) => {
return this.processRegistrations(_response);
});
}
protected processRegistrations(response: Response): Promise<ReferentieResponse> {
const status = response.status;
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
if (status === 200) {
return response.text().then((_responseText) => {
let result200: any = null;
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as ReferentieResponse;
return result200;
});
} else if (status === 422) {
return response.text().then((_responseText) => {
let result422: any = null;
result422 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as ProblemDetails;
return throwException("Unprocessable Content", status, _responseText, _headers, result422);
});
} else if (status !== 200 && status !== 204) {
return response.text().then((_responseText) => {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve<ReferentieResponse>(null as any);
}
/**
* @return OK
*/
@@ -2237,11 +2191,6 @@ export interface ReferentieResponse {
referentie?: string | undefined;
}
export interface RegistratieRequest {
diplomaHerkomst?: string | undefined;
documents?: DocumentRefDto[] | undefined;
}
export interface RegistrationDto {
bigNummer?: string | undefined;
naam?: string | undefined;