feat(260929-if2): Erinnerung zusaetzlich per E-Mail, Doku und Aenderungsliste
- E-Mail-Planer: Anspruch vor dem Senden (genau eine Mail je Faelligkeit, hoechstens 3 Versuche), Systemlesen nur fuer die Kandidatenabfrage - MailService.sendReminderEmail (Berliner Zeit, nur Text), GET /reminders/email-status, Haken im Formular mit Erklaerung - Zugriffsklassifikation und Erlaubnisliste fuer forSystem nachgezogen, Aenderungsliste und Anwenderanleitung Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,10 @@ import { RemindersController } from './reminders.controller';
|
||||
function makeService() {
|
||||
return {
|
||||
list: vi.fn(async (..._args: unknown[]) => []),
|
||||
getEmailAvailability: vi.fn(async (..._args: unknown[]) => ({
|
||||
smtpConfigured: true,
|
||||
hasEmail: true,
|
||||
})),
|
||||
create: vi.fn(async (..._args: unknown[]) => ({})),
|
||||
update: vi.fn(async (..._args: unknown[]) => ({})),
|
||||
snooze: vi.fn(async (..._args: unknown[]) => ({})),
|
||||
@@ -20,7 +24,7 @@ const user = { id: 'u1', username: 'u', role: 'USER', tenantId: 't1' } as any;
|
||||
const proto = RemindersController.prototype as any;
|
||||
|
||||
describe('RemindersController — Rollen', () => {
|
||||
it.each(['list', 'create', 'update', 'snooze', 'remove'])('%s traegt keine Routen-Rolle (jeder Angemeldete)', (name) => {
|
||||
it.each(['list', 'emailStatus', 'create', 'update', 'snooze', 'remove'])('%s traegt keine Routen-Rolle (jeder Angemeldete)', (name) => {
|
||||
expect(Reflect.getMetadata(ROLES_KEY, proto[name])).toBeUndefined();
|
||||
});
|
||||
|
||||
@@ -34,6 +38,8 @@ describe('RemindersController — Mandant', () => {
|
||||
const service = makeService();
|
||||
const controller = new RemindersController(service as any);
|
||||
await controller.list(req('t1'), user);
|
||||
await controller.emailStatus(req('t1'), user);
|
||||
expect(service.getEmailAvailability).toHaveBeenCalledWith('t1', 'u1');
|
||||
await controller.create(req('t1'), user, { title: 'a', dueAt: '2099-01-01T10:00:00.000Z' });
|
||||
expect(service.list).toHaveBeenCalledWith('t1', 'u1');
|
||||
expect(service.create.mock.calls[0].slice(0, 2)).toEqual(['t1', 'u1']);
|
||||
@@ -48,6 +54,7 @@ describe('RemindersController — Mandant', () => {
|
||||
it('wirft ForbiddenException ohne req.tenantId', async () => {
|
||||
const controller = new RemindersController(makeService() as any);
|
||||
await expect(controller.list(req(), user)).rejects.toBeInstanceOf(ForbiddenException);
|
||||
await expect(controller.emailStatus(req(), user)).rejects.toBeInstanceOf(ForbiddenException);
|
||||
await expect(controller.update(req(), user, 'x', {})).rejects.toBeInstanceOf(ForbiddenException);
|
||||
await expect(
|
||||
controller.snooze(req(), user, 'x', { dueAt: '2099-01-01T10:00:00.000Z' }),
|
||||
@@ -102,6 +109,28 @@ describe('RemindersController — Pipe fuer Aendern und Verschieben', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('RemindersController — emailEnabled', () => {
|
||||
it('die Pipe laesst emailEnabled beim Anlegen und Aendern durch und verlangt einen Wahrheitswert', async () => {
|
||||
const pipe = new ValidationPipe({ whitelist: true, transform: true });
|
||||
const created: any = await pipe.transform(
|
||||
{ title: 'a', dueAt: '2099-01-01T10:00:00.000Z', emailEnabled: true },
|
||||
{ type: 'body', metatype: CreateReminderDto },
|
||||
);
|
||||
expect(created.emailEnabled).toBe(true);
|
||||
const updated: any = await pipe.transform(
|
||||
{ emailEnabled: false },
|
||||
{ type: 'body', metatype: UpdateReminderDto },
|
||||
);
|
||||
expect(updated.emailEnabled).toBe(false);
|
||||
await expect(
|
||||
pipe.transform(
|
||||
{ title: 'a', dueAt: '2099-01-01T10:00:00.000Z', emailEnabled: 'ja' },
|
||||
{ type: 'body', metatype: CreateReminderDto },
|
||||
),
|
||||
).rejects.toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('RemindersController — Routen-Reihenfolge (statisch vor :id)', () => {
|
||||
it('deklariert list vor jeder :id-Route', () => {
|
||||
const methods = Object.getOwnPropertyNames(RemindersController.prototype);
|
||||
@@ -111,7 +140,11 @@ describe('RemindersController — Routen-Reihenfolge (statisch vor :id)', () =>
|
||||
const path = Reflect.getMetadata('path', proto[name]);
|
||||
if (typeof path === 'string' && path.startsWith(':id')) {
|
||||
expect(listIdx).toBeLessThan(methods.indexOf(name));
|
||||
// auch die statische Route email-status steht vor jeder :id-Route
|
||||
expect(methods.indexOf('emailStatus')).toBeLessThan(methods.indexOf(name));
|
||||
}
|
||||
}
|
||||
expect(methods.indexOf('emailStatus')).toBeGreaterThanOrEqual(0);
|
||||
expect(Reflect.getMetadata('path', proto.emailStatus)).toBe('email-status');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user