714f731ac9
Neue Felder loginHintDirectory/loginHintLocal (Platzhalter, Pflicht, max. 1000), Migration 20260930170000 (nullable, leer = Standard aus @tessera/shared). Knopf Passwort festlegen und Gueltigkeitshinweis bleiben fest; local-no-link wird nie erzeugt und bleibt fest. Vorschau springt beim Bearbeiten auf die passende Kontoart. Lokal im Browser nachgewiesen. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
214 lines
8.3 KiB
TypeScript
214 lines
8.3 KiB
TypeScript
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,
|
|
};
|
|
|
|
/** Standard-Anmeldehinweise, damit Vorlagen im Test vollstaendig sind. */
|
|
const hints = {
|
|
loginHintDirectory: DEFAULT_WELCOME_MAIL_TEXTS.loginHintDirectory,
|
|
loginHintLocal: DEFAULT_WELCOME_MAIL_TEXTS.loginHintLocal,
|
|
};
|
|
|
|
const localAccount = {
|
|
kind: 'local',
|
|
setPasswordUrl: 'https://tessera.example.invalid/reset-password/abc',
|
|
validHours: 168,
|
|
} as const;
|
|
|
|
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>',
|
|
...hints,
|
|
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', ...hints, 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: '', ...hints, 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('Anmeldehinweis je Kontoart', () => {
|
|
it('Standard: Verzeichniskonto bekommt den Windows-Hinweis, lokales Konto den Hinweis vor dem Knopf', () => {
|
|
const dir = renderWelcomeMail(base);
|
|
expect(dir.html).toContain('gewohnten Windows-Passwort');
|
|
expect(dir.html).not.toContain('persönliches Passwort fest');
|
|
const local = renderWelcomeMail({ ...base, account: localAccount });
|
|
expect(local.html).toContain('legen Sie bitte Ihr persönliches Passwort fest');
|
|
expect(local.html).not.toContain('Windows-Passwort');
|
|
// Hinweis steht vor dem Knopf
|
|
expect(local.html.indexOf('persönliches Passwort fest')).toBeLessThan(
|
|
local.html.indexOf('Passwort festlegen</a>'),
|
|
);
|
|
});
|
|
|
|
it('eigener Text je Kontoart: Platzhalter ersetzt, escaped, Leerzeile = Absatz; Knopf und Gueltigkeit bleiben fest', () => {
|
|
const texts = {
|
|
...DEFAULT_WELCOME_MAIL_TEXTS,
|
|
loginHintDirectory:
|
|
'Anmeldung als {{benutzername}} mit <b>Firmenkennwort</b>.\n\nFragen? IT anrufen.',
|
|
loginHintLocal: 'Hallo {{vorname}}, bitte zuerst ein Passwort setzen.',
|
|
};
|
|
const dir = renderWelcomeMail({ ...base, texts });
|
|
expect(dir.html).toContain(
|
|
'Anmeldung als max.muster mit <b>Firmenkennwort</b>.</p>',
|
|
);
|
|
expect(dir.html).toContain('>Fragen? IT anrufen.</p>');
|
|
expect(dir.html).not.toContain('Windows-Passwort');
|
|
expect(dir.html).not.toContain('bitte zuerst ein Passwort setzen');
|
|
expect(dir.text).toContain(
|
|
'Anmeldung als max.muster mit <b>Firmenkennwort</b>.\n\nFragen? IT anrufen.',
|
|
);
|
|
|
|
const local = renderWelcomeMail({ ...base, account: localAccount, texts });
|
|
expect(local.html).toContain('Hallo Max, bitte zuerst ein Passwort setzen.');
|
|
expect(local.html).not.toContain('Firmenkennwort');
|
|
expect(local.html).toContain('reset-password/abc');
|
|
expect(local.html).toContain('7 Tage');
|
|
expect(local.text).toContain('Hallo Max, bitte zuerst ein Passwort setzen.');
|
|
|
|
const example = renderWelcomeMail({ ...base, account: { kind: 'local-example' }, texts });
|
|
expect(example.html).toContain('Hallo Max, bitte zuerst ein Passwort setzen.');
|
|
});
|
|
|
|
it('leerer oder fehlender Hinweis (aeltere Aufrufer) → Standardtext', () => {
|
|
const { loginHintDirectory: _d, loginHintLocal: _l, ...old } = DEFAULT_WELCOME_MAIL_TEXTS;
|
|
const dir = renderWelcomeMail({ ...base, texts: old as any });
|
|
expect(dir.html).toContain('gewohnten Windows-Passwort');
|
|
const local = renderWelcomeMail({
|
|
...base,
|
|
account: localAccount,
|
|
texts: { ...DEFAULT_WELCOME_MAIL_TEXTS, loginHintLocal: ' ' },
|
|
});
|
|
expect(local.html).toContain('persönliches Passwort fest');
|
|
});
|
|
|
|
it('local-no-link behaelt den festen Hinweis', () => {
|
|
const mail = renderWelcomeMail({ ...base, account: { kind: 'local-no-link' } });
|
|
expect(mail.html).toContain('Ihr Startpasswort erhalten Sie von Ihrem Administrator.');
|
|
});
|
|
});
|
|
|
|
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;');
|
|
});
|
|
});
|