Files

170 lines
9.9 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package replyclassify
import (
"strings"
"testing"
)
const quotedTail = "\n> Hello Anna, a short note about working together.\n> Reply stop to unsubscribe."
// Each client's attribution line, as Gmail, Thunderbird or Apple Mail writes
// it in that language, ends the reply once the workspace reads that language.
// base marks the ones every workspace has always had.
func TestStripQuotedAttributionsByLanguage(t *testing.T) {
for _, c := range attributionCases {
body := "The reply itself.\n\n" + c.line + quotedTail
if got := StripQuoted(body, c.lang); got != "The reply itself." {
t.Errorf("%s: StripQuoted = %q", c.lang, got)
}
if IsOptOut("Re: Hello", StripQuoted(body, c.lang)) {
t.Errorf("%s: the quoted footer opted out", c.lang)
}
// Without the language only the base set reads it.
if stripped := StripQuoted(body) == "The reply itself."; stripped != c.base {
t.Errorf("%s: read without the language = %v, want %v: %q", c.lang, stripped, c.base, c.line)
}
}
}
var attributionCases = []struct {
lang, line string
base bool
}{
{"en", "On Mon, 3 Mar 2025 at 10:12, Anna <anna@example.com> wrote:", true},
{"de", "Am Mo., 3. März 2025 um 10:12 Uhr schrieb Anna <anna@example.com>:", true},
{"fr", "Le lun. 3 mars 2025 à 10:12, Anna <anna@example.com> a écrit :", true},
{"es", "El lun, 3 mar 2025 a las 10:12, Anna (<anna@example.com>) escribió:", true},
{"it", "Il giorno lun 3 mar 2025 alle ore 10:12 Anna <anna@example.com> ha scritto:", true},
{"nl", "Op ma 3 mrt 2025 om 10:12 schreef Anna <anna@example.com>:", true},
{"pt", "Em seg., 3 de mar. de 2025 às 10:12, Anna <anna@example.com> escreveu:", false},
{"sv", "Den mån 3 mars 2025 kl 10:12 skrev Anna <anna@example.com>:", false},
{"da", "Den man. 3. mar. 2025 kl. 10.12 skrev Anna <anna@example.com>:", false},
{"nb", "man. 3. mar. 2025 kl. 10:12 skrev Anna <anna@example.com>:", false},
{"fi", "ma 3. maalisk. 2025 klo 10.12 Anna (anna@example.com) kirjoitti:", false},
{"pl", "pon., 3 mar 2025 o 10:12 Anna <anna@example.com> napisał(a):", false},
{"pl", "W dniu 3.03.2025 o 10:12, Anna pisze:", false},
{"cs", "po 3. 3. 2025 v 10:12 odesílatel Anna <anna@example.com> napsal:", false},
{"ro", "Pe lun., 3 mar. 2025 la 10:12, Anna <anna@example.com> a scris:", false},
{"hu", "Anna <anna@example.com> ezt írta (időpont: 2025. márc. 3., H, 10:12):", false},
{"hu", "2025. 03. 03. 10:12 keltezéssel, Anna írta:", false},
{"ru", "пн, 3 мар. 2025 г. в 10:12, Anna <anna@example.com>:", false},
{"ru", "03.03.2025 10:12, Anna пишет:", false},
{"uk", "пн, 3 бер. 2025 р. о 10:12 Anna <anna@example.com> пише:", false},
{"el", "Στις Δευ 3 Μαρ 2025 στις 10:12, ο/η Anna <anna@example.com> έγραψε:", false},
{"tr", "Anna <anna@example.com>, 3 Mar 2025 Pzt, 10:12 tarihinde şunu yazdı:", false},
{"ja", "2025年3月3日(月) 10:12 Anna <anna@example.com>:", false},
{"zh", "Anna <anna@example.com> 于2025年3月3日周一 10:12写道:", false},
{"zh", "Anna <anna@example.com> 於 2025年3月3日 週一 上午10:12寫道:", false},
{"ko", "2025년 3월 3일 (월) 오전 10:12, Anna <anna@example.com>님이 작성:", false},
{"id", "Pada Sen, 3 Mar 2025 pukul 10.12 Anna <anna@example.com> menulis:", false},
{"vi", "Vào Th 2, 3 thg 3, 2025 vào lúc 10:12 Anna <anna@example.com> đã viết:", false},
{"th", "เมื่อ จ. 3 มี.ค. 2025 เวลา 10:12 Anna <anna@example.com> เขียนว่า:", false},
{"hi", "सोम, 3 मार्च 2025 को 10:12 am बजे Anna <anna@example.com> ने लिखा:", false},
{"ar", "في الاثنين، 3 مارس 2025 في 10:12 ص كتب Anna <anna@example.com>:", false},
{"he", "בתאריך יום ב׳, 3 במרץ 2025 ב-10:12 מאת Anna <anna@example.com>:", false},
{"bg", "пн, 3.03.2025 г. в 10:12 ч. Anna <anna@example.com> написа:", false},
{"bn", "সোম, ৩ মার্চ, ২০২৫ এ ১০:১২ AM এ Anna <anna@example.com> লিখেছেন:", false},
{"ca", "El dl., 3 de març 2025 a les 10:12, Anna (<anna@example.com>) va escriure:", false},
{"et", "E, 3. märts 2025 kell 10:12 Anna <anna@example.com> kirjutas:", false},
{"fa", "در تاریخ دوشنبه ۳ مارس ۲۰۲۵ ساعت ۱۰:۱۲ Anna <anna@example.com> نوشت:", false},
{"fil", "Noong Lun, Mar 3, 2025 nang 10:12 AM, si Anna <anna@example.com> ay sumulat:", false},
{"hr", "pon, 3. ožu 2025. u 10:12 Anna <anna@example.com> napisao je:", false},
{"lt", "2025-03-03, pr, 10:12 Anna <anna@example.com> rašė:", false},
{"lv", "pirmd., 2025. g. 3. marts, plkst. 10:12 lietotājs Anna (<anna@example.com>) rakstīja:", false},
{"ms", "Pada Isn, 3 Mac 2025 pada 10:12 PG, Anna <anna@example.com> menulis:", false},
{"sk", "po 3. 3. 2025 o 10:12 Anna <anna@example.com> napísal(a):", false},
{"sl", "pon., 3. mar. 2025 ob 10:12 je Anna <anna@example.com> napisal(a):", false},
{"sr", "пон, 3. мар 2025. у 10:12 Anna <anna@example.com> је написао/ла:", false},
{"sw", "Mnamo Jum 3 Mac 2025 saa 10:12, Anna <anna@example.com> aliandika:", false},
{"ta", "திங்., 3 மார்., 2025, முற்பகல் 10:12 அன்று, Anna <anna@example.com> எழுதியது:", false},
{"ur", "پیر، 3 مارچ، 2025 کو 10:12 AM Anna <anna@example.com> نے لکھا:", false},
}
// Outlook's quoted header block, with that language's labels.
func TestStripQuotedOutlookBlocksByLanguage(t *testing.T) {
cases := map[string][2]string{
"de": {"Von:", "Gesendet:"}, "fr": {"De :", "Envoyé :"}, "es": {"De:", "Enviado:"}, "pt": {"De:", "Enviada em:"},
"it": {"Da:", "Inviato:"}, "nl": {"Van:", "Verzonden:"}, "sv": {"Från:", "Skickat:"},
"da": {"Fra:", "Sendt:"}, "fi": {"Lähettäjä:", "Lähetetty:"}, "pl": {"Od:", "Wysłano:"},
"cs": {"Od:", "Odesláno:"}, "ro": {"De la:", "Trimis:"}, "hu": {"Feladó:", "Elküldve:"},
"ru": {"От:", "Отправлено:"}, "uk": {"Від:", "Надіслано:"}, "el": {"Από:", "Στάλθηκε:"},
"tr": {"Kimden:", "Gönderildi:"}, "ja": {"差出人:", "送信日時:"}, "zh": {"发件人:", "发送时间:"},
"ko": {"보낸 사람:", "보낸 날짜:"}, "id": {"Dari:", "Dikirim:"},
"vi": {"Từ:", "Đã gửi:"}, "ar": {"من:", "تاريخ الإرسال:"}, "he": {"מאת:", "נשלח:"},
"bg": {"От:", "Изпратено:"}, "ca": {"De:", "Enviat el:"}, "et": {"Saatja:", "Saadetud:"},
"fa": {"از:", "ارسال شده:"}, "hr": {"Šalje:", "Poslano:"}, "lt": {"Nuo:", "Išsiųsta:"},
"lv": {"No:", "Nosūtīts:"}, "ms": {"Daripada:", "Dihantar:"}, "sk": {"Od:", "Odoslané:"},
"sl": {"Od:", "Poslano:"}, "sr": {"Од:", "Послато:"},
}
for lang, labels := range cases {
body := "The reply itself.\n\n" + labels[0] + " Anna <anna@example.com>\n" + labels[1] + " 3.3.2025 10:12\nTo: Max" + quotedTail
if got := StripQuoted(body, lang); got != "The reply itself." {
t.Errorf("%s: StripQuoted = %q", lang, got)
}
if StripQuoted(body) == "The reply itself." {
t.Errorf("%s: read without the language", lang)
}
}
}
// GMX and web.de write the date above the sender.
func TestStripQuotedGermanDateFirstBlock(t *testing.T) {
body := "The reply itself.\n\nGesendet: Montag, 03. März 2025 um 10:12 Uhr\nVon: \"Anna\" <anna@example.com>\nAn: Max" + quotedTail
if got := StripQuoted(body, "de"); got != "The reply itself." {
t.Errorf("StripQuoted = %q", got)
}
if StripQuoted(body) == "The reply itself." {
t.Error("read without the language")
}
}
func TestStripQuotedSeparatorsByLanguage(t *testing.T) {
for sep, lang := range map[string]string{
"-----Original Message-----": "", "-----Message d'origine-----": "fr", "-----Mensaje original-----": "es",
"---------- Mensagem encaminhada ---------": "pt", "-----Oorspronkelijk bericht-----": "nl",
"-----Исходное сообщение-----": "ru", "-----原始邮件-----": "zh", "---------- 転送されたメッセージ ---------": "ja",
"-----Oryginalna wiadomość-----": "pl", "-----Αρχικό μήνυμα-----": "el",
"-------- Original-Nachricht --------": "de",
} {
body := "The reply itself.\n\n" + sep + "\nAnna" + quotedTail
if got := StripQuoted(body, lang); got != "The reply itself." {
t.Errorf("%s: StripQuoted = %q", sep, got)
}
if stripped := StripQuoted(body) == "The reply itself."; stripped != (lang == "") {
t.Errorf("%s: read without the language = %v", sep, stripped)
}
}
}
// Prose that mentions a date and someone writing is the reply, not a quote,
// even for a workspace that reads that language.
func TestStripQuotedKeepsProseInEveryLanguage(t *testing.T) {
all := LanguagesWithRules()
for _, prose := range []string{
"Tack, vi hörs den 3 mars 2025 kl 10.",
"Спасибо, в 2024 году клиент пишет: всё хорошо.",
"شكرا، في 2024 كتب لنا العميل: كل شيء جيد.",
"Ευχαριστώ, στις 3 Μαρτίου 2025 ο πελάτης έγραψε: όλα καλά.",
"धन्यवाद, 2025 को उन्होंने ने लिखा: सब ठीक है।",
"תודה, בתאריך 3.3.2025 קיבלנו מכתב מאת הלקוח.",
"Od: jutra jestem dostępny, zapraszam.",
"Obrigado, em 2024 o cliente escreveu muito.",
"Давайте созвонимся 12 марта 2026 г. в 14:00, если вам удобно.",
"2026年3月12日(木) 14:00 からいかがでしょうか。",
} {
if got := StripQuoted(prose, all...); got != prose {
t.Errorf("prose cut: %q -> %q", prose, got)
}
}
}
// A body stored on one line has no line to go back to, so an attribution that
// opens with a name or a weekday cuts at the match and the reply survives.
func TestStripQuotedOneLineBodyKeepsTheReply(t *testing.T) {
body := "Dziękuję, zgoda. pon., 3 mar 2025 o 10:12 Anna <anna@example.com> napisał(a): > Hello"
got := StripQuoted(body, "pl")
if !strings.HasPrefix(got, "Dziękuję, zgoda.") || strings.Contains(got, "napisał") {
t.Fatalf("StripQuoted = %q", got)
}
}