feat(admin): eigene Vorlage fuer die Willkommensmail mit Platzhaltern, Vorschau und Testmail
Administrator -> Willkommensmail: Betreff, Ueberschrift, Einleitung, Abschluss je
Mandant (Tabelle WelcomeMailTemplate, RLS je Mandant, Migration 20260930150000);
Platzhalter {{name}} {{vorname}} {{benutzername}} {{email}} {{adresse}} {{firma}},
unbekannte -> 400 bzw. Hinweis beim Tippen; Werte escaped, Vorlage reiner Text.
Live-Vorschau per API gerendert, Testmail an die eigene Adresse ohne Token,
Zuruecksetzen auf Standard. Feste Bausteine (Kopf, Zugangsdaten, Anmeldehinweis,
Knoepfe, Fusszeile) bleiben immer drin.
Kopf: Wellenzelle dunkel statt weiss, Streifen 600x40, Inhalt 24 px naeher –
keine weisse Luecke, wenn OWA das CID-Bild nicht zeigt.
Lokal nachgewiesen: Hinweis/Sperre bei {{xyz}}, Speichern, Testmail (Link nur
/login), echte Mail mit eigener Vorlage und 7-Tage-Link.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
import { DEFAULT_WELCOME_MAIL_TEXTS, findUnknownWelcomeMailPlaceholders } from '@tessera/shared';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
applyWelcomeMailPlaceholders,
|
||||
firstNameOf,
|
||||
renderWelcomeMail,
|
||||
type WelcomeMailInput,
|
||||
} from './welcome-mail.template';
|
||||
|
||||
/**
|
||||
* Willkommensmail mit eigener Vorlage: Platzhalter, Escaping, Absaetze,
|
||||
* feste Bausteine.
|
||||
*/
|
||||
|
||||
const base: WelcomeMailInput = {
|
||||
name: 'Max Muster',
|
||||
username: 'max.muster',
|
||||
email: 'max@example.invalid',
|
||||
tenantName: 'Muster & Co',
|
||||
appUrl: 'https://tessera.example.invalid/',
|
||||
account: { kind: 'directory' },
|
||||
headerImageSrc: null,
|
||||
};
|
||||
|
||||
const values = {
|
||||
name: 'Max Muster',
|
||||
vorname: 'Max',
|
||||
benutzername: 'max.muster',
|
||||
email: 'max@example.invalid',
|
||||
adresse: 'https://t.example.invalid',
|
||||
firma: 'Muster & Co',
|
||||
};
|
||||
|
||||
describe('Platzhalter', () => {
|
||||
it('ersetzt alle sechs, Gross-/Kleinschreibung und Leerraum egal; unbekannte bleiben stehen', () => {
|
||||
expect(
|
||||
applyWelcomeMailPlaceholders(
|
||||
'{{name}}|{{vorname}}|{{benutzername}}|{{email}}|{{adresse}}|{{firma}}|{{ NAME }}|{{xyz}}',
|
||||
values,
|
||||
),
|
||||
).toBe(
|
||||
'Max Muster|Max|max.muster|max@example.invalid|https://t.example.invalid|Muster & Co|Max Muster|{{xyz}}',
|
||||
);
|
||||
});
|
||||
|
||||
it('findUnknownWelcomeMailPlaceholders nennt jeden unbekannten einmal', () => {
|
||||
expect(findUnknownWelcomeMailPlaceholders('{{a}} {{name}} {{a}} {{ b }} {{}}')).toEqual([
|
||||
'{{a}}',
|
||||
'{{b}}',
|
||||
'{{}}',
|
||||
]);
|
||||
expect(findUnknownWelcomeMailPlaceholders(DEFAULT_WELCOME_MAIL_TEXTS.heading)).toEqual([]);
|
||||
});
|
||||
|
||||
it('vorname: erstes Wort des Anzeigenamens, sonst Benutzername', () => {
|
||||
expect(firstNameOf(' Erika Mustermann ', 'erika')).toBe('Erika');
|
||||
expect(firstNameOf(null, 'erika')).toBe('erika');
|
||||
expect(firstNameOf(' ', 'erika')).toBe('erika');
|
||||
});
|
||||
});
|
||||
|
||||
describe('renderWelcomeMail mit Vorlage', () => {
|
||||
it('ohne Vorlage: Standard-Betreff und -Ueberschrift', () => {
|
||||
const mail = renderWelcomeMail(base);
|
||||
expect(mail.subject).toBe('Willkommen bei Tessera');
|
||||
expect(mail.html).toContain('Willkommen bei Tessera, Max Muster!');
|
||||
expect(mail.html).toContain('Viel Erfolg mit Tessera!');
|
||||
});
|
||||
|
||||
it('Text ist reiner Text: HTML aus Vorlage und Werten wird escaped; Firma mit & korrekt', () => {
|
||||
const mail = renderWelcomeMail({
|
||||
...base,
|
||||
name: '<script>x</script>',
|
||||
texts: {
|
||||
subject: 'Hallo\r\n{{firma}}',
|
||||
heading: '<h2>{{name}}</h2>',
|
||||
intro: '<a href="javascript:alert(1)">klick</a>',
|
||||
closing: '',
|
||||
},
|
||||
});
|
||||
expect(mail.subject).toBe('Hallo Muster & Co');
|
||||
expect(mail.html).not.toContain('<script>x</script>');
|
||||
expect(mail.html).not.toContain('<h2>');
|
||||
expect(mail.html).not.toContain('href="javascript:');
|
||||
expect(mail.html).toContain('<h2><script>x</script></h2>');
|
||||
expect(mail.html).toContain('<title>Hallo Muster & Co</title>');
|
||||
});
|
||||
|
||||
it('Leerzeile = neuer Absatz, einfacher Umbruch = <br>; leerer Abschluss faellt weg', () => {
|
||||
const mail = renderWelcomeMail({
|
||||
...base,
|
||||
texts: { subject: 's', heading: 'h', intro: 'A1\nA2\r\n\r\n\nB', closing: ' ' },
|
||||
});
|
||||
expect(mail.html).toContain('>A1<br>A2</p>');
|
||||
expect(mail.html).toContain('>B</p>');
|
||||
expect(mail.html).not.toContain('Viel Erfolg');
|
||||
expect(mail.text).toContain('A1\nA2\n\nB');
|
||||
});
|
||||
|
||||
it('feste Bausteine bleiben bei jeder Vorlage: Adresse, Benutzername, Anmeldehinweis, Zu Tessera, Fusszeile', () => {
|
||||
const mail = renderWelcomeMail({
|
||||
...base,
|
||||
account: {
|
||||
kind: 'local',
|
||||
setPasswordUrl: 'https://tessera.example.invalid/reset-password/abc',
|
||||
validHours: 168,
|
||||
},
|
||||
texts: { subject: 'x', heading: 'y', intro: '', closing: '' },
|
||||
});
|
||||
expect(mail.html).toContain('https://tessera.example.invalid/reset-password/abc');
|
||||
expect(mail.html).toContain('Passwort festlegen');
|
||||
expect(mail.html).toContain('7 Tage');
|
||||
expect(mail.html).toContain('Zu Tessera');
|
||||
expect(mail.html).toContain('max.muster');
|
||||
expect(mail.html).toContain('im Auftrag Ihres Administrators');
|
||||
});
|
||||
|
||||
it('local-example: Knopf zur Anmeldeseite mit Beispiel-Hinweis, kein reset-password', () => {
|
||||
const mail = renderWelcomeMail({
|
||||
...base,
|
||||
account: { kind: 'local-example' },
|
||||
notice: 'Testmail',
|
||||
});
|
||||
expect(mail.html).toContain('Beispiel-Link');
|
||||
expect(mail.html).not.toContain('reset-password');
|
||||
expect(mail.html).toContain('Testmail');
|
||||
expect(mail.text.startsWith('[Testmail]')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Kopf (Outlook ohne CID-Bild)', () => {
|
||||
it('Wellenstreifen 600x40 in einer Zelle mit dunkler Kopffarbe, Inhalt beginnt mit 24px Abstand', () => {
|
||||
const mail = renderWelcomeMail({ ...base, headerImageSrc: 'cid:welcome-header@tessera' });
|
||||
expect(mail.html).toMatch(
|
||||
/<td bgcolor="#1a1c20" style="background-color:#1a1c20;line-height:0;font-size:0;">\n<img src="cid:welcome-header@tessera" width="600" height="40"/,
|
||||
);
|
||||
expect(mail.html).toContain('padding:24px 40px 12px;');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user