package backend import ( "strings" "testing" "github.com/emersion/go-imap/v2" ) func TestSanitizeIMAPFlagsDropsRecentAndWildcard(t *testing.T) { got := sanitizeIMAPFlags([]imap.Flag{imap.FlagSeen, imap.Flag(`\Recent`), imap.FlagWildcard, imap.FlagAnswered}) if len(got) != 2 || got[0] != imap.FlagSeen || got[1] != imap.FlagAnswered { t.Fatalf("sanitizeIMAPFlags returned %#v", got) } } func TestMessageIDFallsBackToBodyHash(t *testing.T) { body := []byte("From: a@example.com\r\nSubject: Test\r\n\r\nBody") id := messageID(body) if !strings.HasPrefix(id, "sha256:") { t.Fatalf("expected sha256 fallback, got %q", id) } if id != messageID(body) { t.Fatal("messageID hash fallback is not stable") } } func TestNormalizeMessageIDExtractsBracketedIDBeforeBrokenComment(t *testing.T) { tests := map[string]string{ "": "normal@example.com", "bracketless@example.com": "bracketless@example.com", " <5486CB7400AF1FB7@mr001msb.fastweb.it> (added by postmaster) ": "5486CB7400AF1FB7@mr001msb.fastweb.it", "comment trailing": "embedded@example.com", } for input, want := range tests { if got := normalizeMessageID(input); got != want { t.Errorf("normalizeMessageID(%q)=%q, want %q", input, got, want) } } } func TestIdentityKeepsOldMalformedHeaderNormalizationAsAlias(t *testing.T) { tests := []struct { header string clean string legacy string }{ { header: `<476BDFDB019289BE@mail21.bluewin.ch> (added by postmaster@bluewin.ch)`, clean: `476BDFDB019289BE@mail21.bluewin.ch`, legacy: `476BDFDB019289BE@mail21.bluewin.ch> (added by postmaster@bluewin.ch)`, }, { header: `<22578196653698419217.BB8A0E8F48F3F290@dr-gold.de>+D271B1409C3C6028`, clean: `22578196653698419217.BB8A0E8F48F3F290@dr-gold.de`, legacy: `22578196653698419217.BB8A0E8F48F3F290@dr-gold.de>+D271B1409C3C6028`, }, } for _, tt := range tests { body := []byte("Message-ID: " + tt.header + "\r\nSubject: malformed ID\r\n\r\nbody") identity := identityForRawMessage(RawMessage{MessageID: messageID(body), Body: body}) if identity.MessageID != tt.clean { t.Fatalf("MessageID=%q, want %q", identity.MessageID, tt.clean) } found := false for _, alias := range identity.LegacyMessageIDs { if alias == tt.legacy { found = true break } } if !found { t.Fatalf("legacy alias %q missing from %#v", tt.legacy, identity.LegacyMessageIDs) } } } func TestBodySHA256IsStableAcrossMboxNormalization(t *testing.T) { raw := []byte("From: a@example.com\r\nSubject: Test\r\n\r\nFirst\r\nFrom escaped\r\n\r\n") record := mboxRecord(RawMessage{Body: raw}) parts := splitMboxRecordsWithOffsets(record) if len(parts) != 1 { t.Fatalf("mbox parts=%d, want 1", len(parts)) } if got, want := bodySHA256(parts[0].Message), bodySHA256(raw); got != want { t.Fatalf("mbox hash=%s, raw hash=%s", got, want) } } func TestIdentityUsesMessageIDAndBodyHash(t *testing.T) { first := identityForRawMessage(RawMessage{MessageID: "", Body: []byte("Message-ID: \r\n\r\nfirst")}) second := identityForRawMessage(RawMessage{MessageID: "", Body: []byte("Message-ID: \r\n\r\nsecond")}) if first.MessageID != second.MessageID { t.Fatalf("message IDs differ: %#v %#v", first, second) } if first.BodySHA256 == second.BodySHA256 { t.Fatalf("different bodies have same identity hash: %#v %#v", first, second) } } func TestIdentityWithoutMessageIDKeepsLegacyRawHash(t *testing.T) { body := []byte("From: a@example.com\r\n\r\nbody") identity := identityForRawMessage(RawMessage{MessageID: messageID(body), Body: body}) if identity.MessageID != "" || identity.BodySHA256 == "" || !strings.HasPrefix(identity.LegacyMessageID, "sha256:") { t.Fatalf("unexpected identity: %#v", identity) } }