## What & why S-15b, second of the S-15 (#16) split, on top of S-15a (#133). A beheerder edits the ACL's ZGW **default-fill** values from the beheer portal, and the next zaak is stamped with the new values — no restart. Closes #131 ### The vertical portal → BFF `GET/PUT /beheer/default-fill` (medewerker realm + `beheerder` role) → ACL `GET/PUT /default-fill` → a runtime-mutable in-memory store the ACL reads **per zaak**. - **ACL**: `IDefaultFillStore` / `InMemoryDefaultFillStore` (thread-safe, seeded from `Acl:Defaults`); `AclService` reads `fill.Current` per zaak (not cached at construction); `GET`/`PUT /default-fill` with required-field validation. - **BFF**: `IAclClient` gains `GetDefaultFillAsync`/`UpdateDefaultFillAsync`; `GET`/`PUT /beheer/default-fill` behind the `beheerder` policy. OpenAPI + generated client regenerated. - **Frontend**: a *Default-fill* editor page in the beheer app (load → edit → save, with saved/failure states) + nav between Catalogus and Default-fill. ### Scope decision → ADR-0026 Only the **three ZGW fill fields** (bronorganisatie, verantwoordelijke organisatie, vertrouwelijkheidaanduiding) are editable. The S-27 catalog-resolution keys stay **static config** — editing them would desync the zaaktype-URL cache (ADR-0021), and they're catalogus wiring, not "default fill". The store is **in-memory** (seeded from config): an edit reverts on restart. That's the reference-app-appropriate ceiling (no DB added to the stateless ACL); upgrade path documented. Recorded in **ADR-0026**. ## Verified locally lint (`dotnet format`) ✓ · .NET unit — acl 60 / bff 45 / domain 152 / event-subscriber 19 / acceptance 17 ✓ · frontend lint+test (8 projects) ✓ · beheer build ✓. Clean full-solution build (caught + fixed the acceptance `AclService` ctor drift). TDD red→green per layer (ACL store, ACL endpoints, BFF, frontend). ## Definition of Done - [x] Failing test committed before each implementation (red→green per layer). - [x] Conventional Commits referencing #131. - [ ] CI green — see note below. - [x] Docs: ADR-0026 + S-15b demo note. - [x] Demo note in `docs/demo-script.md`. ## Note on CI The bulk validates in the fast jobs (lint/build/unit/frontend/mutation). The **verify-stack e2e** (incl. the new `default-fill.spec.ts`) can't go green until the pre-existing **verify-stack bring-up failure on the 1.27/2.0.0 runner** is resolved (that fails on plain `main` too — unrelated to this PR). Additive change; no existing e2e touched. 🤖 Generated with [Claude Code](https://claude.com/claude-code)Reviewed-on: #138
91 lines
3.2 KiB
TypeScript
91 lines
3.2 KiB
TypeScript
import { signal } from '@angular/core';
|
|
import { fireEvent, render, screen } from '@testing-library/angular';
|
|
import { of, throwError } from 'rxjs';
|
|
import { BeheerDefaultFill, BffApiV1Service } from 'api-client';
|
|
import { AuthService } from 'auth';
|
|
import { axe } from 'vitest-axe';
|
|
import { DefaultFillPage } from './default-fill-page';
|
|
|
|
const current: BeheerDefaultFill = {
|
|
bronorganisatie: '517439943',
|
|
verantwoordelijkeOrganisatie: '517439943',
|
|
vertrouwelijkheidaanduiding: 'openbaar',
|
|
};
|
|
|
|
class FakeAuth extends AuthService {
|
|
readonly isAuthenticated = signal(true);
|
|
readonly bsn = signal<string | undefined>(undefined);
|
|
override readonly roles = signal<readonly string[]>(['beheerder']);
|
|
login(): void {
|
|
/* not exercised */
|
|
}
|
|
logout(): void {
|
|
/* not exercised */
|
|
}
|
|
}
|
|
|
|
function setup(
|
|
overrides: {
|
|
getBeheerDefaultFill?: ReturnType<typeof vi.fn>;
|
|
putBeheerDefaultFill?: ReturnType<typeof vi.fn>;
|
|
} = {},
|
|
) {
|
|
const getBeheerDefaultFill = overrides.getBeheerDefaultFill ?? vi.fn().mockReturnValue(of(current));
|
|
const putBeheerDefaultFill = overrides.putBeheerDefaultFill ?? vi.fn().mockReturnValue(of(undefined));
|
|
return {
|
|
getBeheerDefaultFill,
|
|
putBeheerDefaultFill,
|
|
providers: [
|
|
{ provide: BffApiV1Service, useValue: { getBeheerDefaultFill, putBeheerDefaultFill } },
|
|
{ provide: AuthService, useClass: FakeAuth },
|
|
],
|
|
};
|
|
}
|
|
|
|
describe('DefaultFillPage', () => {
|
|
it('loads the current default-fill into the form on open', async () => {
|
|
const { getBeheerDefaultFill, providers } = setup();
|
|
await render(DefaultFillPage, { providers });
|
|
|
|
expect(getBeheerDefaultFill).toHaveBeenCalled();
|
|
const bron = (await screen.findByLabelText('Bronorganisatie')) as HTMLInputElement;
|
|
expect(bron.value).toBe('517439943');
|
|
});
|
|
|
|
it('saves the edited values via the BFF', async () => {
|
|
const { putBeheerDefaultFill, providers } = setup();
|
|
await render(DefaultFillPage, { providers });
|
|
|
|
const bron = (await screen.findByLabelText('Bronorganisatie')) as HTMLInputElement;
|
|
fireEvent.input(bron, { target: { value: '999999999' } });
|
|
fireEvent.click(screen.getByRole('button', { name: /opslaan/i }));
|
|
|
|
expect(putBeheerDefaultFill).toHaveBeenCalledWith(
|
|
expect.objectContaining({ bronorganisatie: '999999999', vertrouwelijkheidaanduiding: 'openbaar' }),
|
|
);
|
|
expect(await screen.findByText(/standaardwaarden zijn opgeslagen/i)).toBeTruthy();
|
|
});
|
|
|
|
it('surfaces a save failure instead of swallowing it', async () => {
|
|
const { providers } = setup({
|
|
putBeheerDefaultFill: vi.fn().mockReturnValue(throwError(() => new Error('403'))),
|
|
});
|
|
await render(DefaultFillPage, { providers });
|
|
|
|
fireEvent.click(await screen.findByRole('button', { name: /opslaan/i }));
|
|
|
|
expect(await screen.findByText(/opslaan is niet gelukt/i)).toBeTruthy();
|
|
});
|
|
|
|
it('has no WCAG 2.1 AA violations', async () => {
|
|
document.documentElement.lang = 'nl';
|
|
const { container } = await render(DefaultFillPage, { providers: setup().providers });
|
|
|
|
const results = await axe(container, {
|
|
runOnly: { type: 'tag', values: ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'] },
|
|
});
|
|
|
|
expect(results.violations).toEqual([]);
|
|
});
|
|
});
|