test(260928-ujj): rote Tests fuer Dashboard-Hintergrund in der Datenbank

- PATCH me/dashboard-background: Allowlist, UUID-Bildkennung, Mandantenbindung
- getMe liefert dashboardBackground normalisiert
- Web: Uebernahme der alten localStorage-Wahl, Hook liest aus dem Auth-Store

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-28 22:08:00 +02:00
parent 9fa0a3f498
commit 76f6d87973
5 changed files with 299 additions and 31 deletions
+83
View File
@@ -580,4 +580,87 @@ describe('UserController', () => {
});
});
});
/**
* quick-260928-ujj: Dashboard-Hintergrund pro Benutzer in der Datenbank.
* Der Selbstbedienungsweg schreibt ausschliesslich die Zeile des
* angemeldeten Benutzers ueber den an dessen Mandanten gebundenen Klienten
* (T-ujj-02) und nimmt nur die erlaubten Formen an (T-ujj-01): gespeichert
* wird immer ein frisch aufgebautes Objekt ohne Zusatzschluessel.
*/
describe('Dashboard-Hintergrund (quick-260928-ujj)', () => {
const me = { role: Role.USER, tenantId: 't1', id: 'u-me', username: 'me', mustChangePassword: false };
const IMAGE_ID = '3f2b8c1e-9a4d-4e7f-8b21-0c5d6e7f8a9b';
function seedMe(tenantId = 't1', id = 'u-me') {
prisma.__seedUser({ id, username: id, tenantId, dashboardBackground: null });
}
async function storedFor(tenantId: string, id: string) {
const row = await prisma.__makeBoundClient(tenantId).user.findUnique({ where: { id } });
return row.dashboardBackground;
}
it.each([
[{ kind: 'none' }, { kind: 'none' }],
[{ kind: 'preset', id: 'dunes' }, { kind: 'preset', id: 'dunes' }],
[{ kind: 'preset', id: 'mosaic', extra: 'weg' }, { kind: 'preset', id: 'mosaic' }],
[{ kind: 'image', imageId: IMAGE_ID }, { kind: 'image', imageId: IMAGE_ID }],
[{ kind: 'image', imageId: IMAGE_ID.toUpperCase(), id: 'mist' }, { kind: 'image', imageId: IMAGE_ID.toUpperCase() }],
[{ kind: 'none', id: 'dunes', imageId: IMAGE_ID }, { kind: 'none' }],
])('%j wird normalisiert als %j gespeichert, gebunden an den eigenen Mandanten', async (input, expected) => {
seedMe();
const result = await controller.updateDashboardBackground({ background: input }, me);
expect(result).toEqual({ success: true, dashboardBackground: expected });
expect(await storedFor('t1', 'u-me')).toEqual(expected);
expectBoundCall(prisma, 't1', 'user', 'update');
expect(prisma.__boundCallLog.filter((c: any) => c.method === 'update').every((c: any) => c.tenantId === 't1')).toBe(true);
});
it.each([
[{ kind: 'preset', id: 'regenbogen' }],
[{ kind: 'preset' }],
[{ kind: 'preset', id: 42 }],
[{ kind: 'gradient', id: 'mist' }],
[{ kind: 'image', imageId: 'img-1' }],
[{ kind: 'image', imageId: '") ; background: url("https://boese.invalid/x' }],
[{ kind: 'image', imageId: `${IMAGE_ID})` }],
[{ kind: 'image', imageId: `${IMAGE_ID}0` }],
[{ kind: 'image', imageId: '' }],
[{ kind: 'image' }],
[{}],
[null],
[undefined],
['none'],
[42],
[[{ kind: 'none' }]],
])('ungueltige Wahl %j → BadRequestException, nichts geschrieben', async (background) => {
seedMe();
await expect(controller.updateDashboardBackground({ background }, me)).rejects.toBeInstanceOf(BadRequestException);
expect(prisma.__boundCallLog.some((c: any) => c.method === 'update')).toBe(false);
expect(await storedFor('t1', 'u-me')).toBeNull();
});
it('fehlender Rumpf → BadRequestException', async () => {
seedMe();
await expect(
controller.updateDashboardBackground(undefined as unknown as { background: unknown }, me),
).rejects.toBeInstanceOf(BadRequestException);
expect(prisma.__boundCallLog.some((c: any) => c.method === 'update')).toBe(false);
});
it('zwei Benutzer in zwei Mandanten: nur die Zeile des Anfragenden aendert sich', async () => {
seedMe('t1', 'u-me');
seedMe('t2', 'u-other');
await controller.updateDashboardBackground({ background: { kind: 'preset', id: 'mist' } }, me);
expect(await storedFor('t1', 'u-me')).toEqual({ kind: 'preset', id: 'mist' });
expect(await storedFor('t2', 'u-other')).toBeNull();
});
});
});