/** * Tests für mail-training-utils.ts — Phase 1 Data-Foundation. * * Abgedeckt: * - sanitizeSubjectForTraining() — alle Regex-Patterns * - detectSubjectLanguage() — franc-Integration * - Edge-Cases: null, leer, nur Whitespace */ import { describe, it, expect } from "vitest"; import { sanitizeSubjectForTraining, detectSubjectLanguage, } from "../../server/utils/mail-training-utils"; // ─── sanitizeSubjectForTraining() ───────────────────────────────────────────── describe("sanitizeSubjectForTraining()", () => { // ─── Edge-Cases ──────────────────────────────────────────────────────────── it("null → leerer String + detectedLang='und'", () => { const result = sanitizeSubjectForTraining(null); expect(result.subjectSanitized).toBe(""); expect(result.detectedLang).toBe("und"); }); it("leerer String → leerer String + detectedLang='und'", () => { const result = sanitizeSubjectForTraining(""); expect(result.subjectSanitized).toBe(""); expect(result.detectedLang).toBe("und"); }); it("nur Whitespace → leerer String", () => { const result = sanitizeSubjectForTraining(" "); expect(result.subjectSanitized).toBe(""); }); // ─── E-Mail-Erkennung ────────────────────────────────────────────────────── it("E-Mail-Adresse im Betreff → [EMAIL]", () => { const result = sanitizeSubjectForTraining("Info für test@example.com"); expect(result.subjectSanitized).toBe("Info für [EMAIL]"); }); it("E-Mail mitten im Satz → [EMAIL]", () => { const result = sanitizeSubjectForTraining("Willkommen user123@domain.de, dein Konto ist aktiv"); expect(result.subjectSanitized).toBe("Willkommen [EMAIL], dein Konto ist aktiv"); }); // ─── URL-Erkennung ───────────────────────────────────────────────────────── it("https-URL → [URL]", () => { const result = sanitizeSubjectForTraining("Besuche https://casino.bet/promo"); expect(result.subjectSanitized).toBe("Besuche [URL]"); }); it("http-URL → [URL]", () => { const result = sanitizeSubjectForTraining("Klicke auf http://example.com/offer"); expect(result.subjectSanitized).toBe("Klicke auf [URL]"); }); it("www-URL → [URL]", () => { const result = sanitizeSubjectForTraining("www.bonus.de/angebot jetzt sichern"); expect(result.subjectSanitized).toBe("[URL] jetzt sichern"); }); // ─── Zahlen-Erkennung ────────────────────────────────────────────────────── it("5-stellige Zahl → [NUM]", () => { const result = sanitizeSubjectForTraining("Bestellung 12345 wurde versandt"); expect(result.subjectSanitized).toBe("Bestellung [NUM] wurde versandt"); }); it("9-stellige Kundennummer → [NUM]", () => { const result = sanitizeSubjectForTraining("Deine Kundennummer: 987654321"); expect(result.subjectSanitized).toBe("Deine Kundennummer: [NUM]"); }); it("4-stellige Zahl bleibt erhalten (Jahr, Score)", () => { const result = sanitizeSubjectForTraining("Angebot 2024 — bis zu 1000€"); expect(result.subjectSanitized).toBe("Angebot 2024 — bis zu 1000€"); }); // ─── Greeting-Prefix ─────────────────────────────────────────────────────── it("'Hallo Max, ...' → Greeting-Prefix stripped", () => { const result = sanitizeSubjectForTraining("Hallo Max, dein Bonus wartet"); expect(result.subjectSanitized).toBe("dein Bonus wartet"); }); it("'Hi Sarah! ...' → Greeting-Prefix stripped", () => { const result = sanitizeSubjectForTraining("Hi Sarah! Neues Angebot für dich"); expect(result.subjectSanitized).toBe("Neues Angebot für dich"); }); it("'Dear John, ...' → Greeting-Prefix stripped", () => { const result = sanitizeSubjectForTraining("Dear John, your account is ready"); expect(result.subjectSanitized).toBe("your account is ready"); }); it("'Cher Michel, ...' → Greeting-Prefix stripped", () => { const result = sanitizeSubjectForTraining("Cher Michel, votre offre expire"); expect(result.subjectSanitized).toBe("votre offre expire"); }); it("kein Greeting → bleibt unverändert (kein false strip)", () => { const result = sanitizeSubjectForTraining("Casino Bonus wartet auf dich"); expect(result.subjectSanitized).toBe("Casino Bonus wartet auf dich"); }); // ─── ALL-CAPS-Wörter ─────────────────────────────────────────────────────── it("ALL-CAPS-Wort >= 4 Zeichen → [NAME]", () => { const result = sanitizeSubjectForTraining("Willkommen HANS, dein Konto ist bereit"); expect(result.subjectSanitized).toBe("Willkommen [NAME], dein Konto ist bereit"); }); it("ALL-CAPS-Wort < 4 Zeichen bleibt (z.B. 'SMS', 'VIP')", () => { const result = sanitizeSubjectForTraining("Dein VIP-Bonus wartet"); // "VIP" ist 3 Zeichen → bleibt erhalten expect(result.subjectSanitized).toBe("Dein VIP-Bonus wartet"); }); // ─── Whitespace-Normalisierung ───────────────────────────────────────────── it("mehrfache Leerzeichen nach Sanitization → normalisiert", () => { // Wenn Greeting entfernt wird können führende Leerzeichen entstehen const result = sanitizeSubjectForTraining("Hallo Test, dein Angebot"); expect(result.subjectSanitized).not.toMatch(/\s{2,}/); }); // ─── Kombinierte Patterns ────────────────────────────────────────────────── it("Kombination: URL + Zahl + E-Mail alle ersetzt", () => { const result = sanitizeSubjectForTraining( "Tracking 123456789: Paket von user@shop.de — Link: https://track.shop.de/abc" ); expect(result.subjectSanitized).toContain("[NUM]"); expect(result.subjectSanitized).toContain("[EMAIL]"); expect(result.subjectSanitized).toContain("[URL]"); expect(result.subjectSanitized).not.toMatch(/\d{5,}/); expect(result.subjectSanitized).not.toMatch(/[a-zA-Z0-9._%+\-]+@/); }); it("realistisches Gambling-Mail bleibt erkennbar nach Sanitization", () => { const result = sanitizeSubjectForTraining( "Dein Glücksspiel-Bonus 100€ wartet — Tracking: 987654321" ); // Gambling-Signal "Glücksspiel-Bonus" bleibt erhalten expect(result.subjectSanitized).toContain("Glücksspiel-Bonus"); // Tracking-ID wird ersetzt expect(result.subjectSanitized).toContain("[NUM]"); expect(result.subjectSanitized).not.toMatch(/\d{5,}/); }); }); // ─── detectSubjectLanguage() ────────────────────────────────────────────────── describe("detectSubjectLanguage()", () => { it("null → 'und'", () => { expect(detectSubjectLanguage(null)).toBe("und"); }); it("leerer String → 'und'", () => { expect(detectSubjectLanguage("")).toBe("und"); }); it("Deutsch erkannt (deu)", () => { // Längerer Text für bessere Spracherkennung const lang = detectSubjectLanguage( "Dein exklusives Angebot wartet auf dich — jetzt einlösen und profitieren" ); expect(lang).toBe("deu"); }); it("Englisch-artiger Text → nicht 'und' (franc erkennt eine Sprache)", () => { // franc kann je nach Text-Sample eng oder sco (Scots) zurückgeben für englische Texte — // beide sind akzeptabel. Wichtig: kein "und" (= unbekannt). const lang = detectSubjectLanguage( "Your exclusive offer is waiting — claim your bonus now and start winning today" ); expect(lang).not.toBe("und"); expect(typeof lang).toBe("string"); }); it("Zu kurzer Text → 'und' (franc-Limitation bei < ~20 Zeichen)", () => { // Sehr kurze Texte liefern "und" oder falsche Erkennungen — erwartetes Verhalten. // Wir prüfen nur dass ein String zurückkommt (kein throw). const lang = detectSubjectLanguage("Hi"); expect(typeof lang).toBe("string"); expect(lang.length).toBeGreaterThan(0); }); });