test(auth): extract and spec the stored-session parse boundary (RB-10)
SessionStore.restore() — identical in both apps — read localStorage itself
and did the parse plus shape validation in the same module-private function,
invoked from a field initializer, so the storage read happened the instant
the singleton was constructed and no spec could feed it a raw string. The
logic it guards is a trust boundary, not incidental validation: the comment
above it names G1 (never persist the BSN) and G2 (validate the shape before
trusting it), and CLAUDE.md mandates a spec for boundary parse* adapters.
ssp/auth and bhp/auth were jointly the worst-covered frontend modules.
parseStoredSession(raw) moves into each app's auth/domain/session.ts, which
is pure TS and already had a spec, so no new scaffolding was needed;
restore() collapses to one line. Four cases: absent, non-JSON, wrong shape,
and — BIO-017's addition — a stored {"bsn":…,"naam":…} restoring with bsn
'', which makes the G1 guarantee executable rather than merely commented.
Verified red without the fix.
Landed twice, once per app, deliberately. TE-001 and BL-002 both say an
extract-to-shared here would contradict ADR-0002, which models the two
actors as different Principal variants and expects the two auth contexts to
diverge; RB-13 is what differentiates them.
Also specs redactProfile (BIO-017's second half) — a pure exported
PII-redaction function that had none.
behaviour-spec.mdx is regenerated, which also picks up the test names RB-07
added; that commit should have carried them and did not.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { BigProfile } from '@registratie/domain/big-profile';
|
||||
import { REDACTED } from '@shared/kernel/pii';
|
||||
import { redactProfile } from './mask';
|
||||
|
||||
const profile: BigProfile = {
|
||||
registration: {
|
||||
bigNummer: '12345678901',
|
||||
naam: 'J. Jansen',
|
||||
beroep: 'arts',
|
||||
registratiedatum: '2015-03-01',
|
||||
geboortedatum: '1980-06-12',
|
||||
status: { tag: 'Geregistreerd', herregistratieDatum: '2027-03-01' },
|
||||
},
|
||||
person: {
|
||||
naam: 'J. Jansen',
|
||||
geboortedatum: '1980-06-12',
|
||||
adres: { straat: 'Hoofdstraat 1', postcode: '1234AB', woonplaats: 'Utrecht' },
|
||||
},
|
||||
};
|
||||
|
||||
describe('redactProfile', () => {
|
||||
const redacted = redactProfile(profile) as {
|
||||
registration: {
|
||||
bigNummer: string;
|
||||
naam: string;
|
||||
beroep: string;
|
||||
registratiedatum: string;
|
||||
geboortedatum: string;
|
||||
status: unknown;
|
||||
};
|
||||
person: { naam: string; geboortedatum: string; adres: string };
|
||||
};
|
||||
|
||||
it('masks the BIG-nummer to its last 3 digits', () => {
|
||||
expect(redacted.registration.bigNummer).toBe('********901');
|
||||
});
|
||||
|
||||
it('redacts the name on both the registration and the person', () => {
|
||||
expect(redacted.registration.naam).toBe(REDACTED);
|
||||
expect(redacted.person.naam).toBe(REDACTED);
|
||||
});
|
||||
|
||||
it('redacts every date of birth', () => {
|
||||
expect(redacted.registration.geboortedatum).toBe(REDACTED);
|
||||
expect(redacted.person.geboortedatum).toBe(REDACTED);
|
||||
});
|
||||
|
||||
it('redacts the address', () => {
|
||||
expect(redacted.person.adres).toBe(REDACTED);
|
||||
});
|
||||
|
||||
it('keeps structural/decision-relevant fields untouched', () => {
|
||||
expect(redacted.registration.beroep).toBe('arts');
|
||||
expect(redacted.registration.registratiedatum).toBe('2015-03-01');
|
||||
expect(redacted.registration.status).toEqual(profile.registration.status);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user