package smtpserver import ( "net/smtp" "strings" "testing" ) func TestBlockedSenderRejectedAtRcpt(t *testing.T) { backend, mailboxID := newTestBackendWithMailbox(t) if _, err := backend.DB.AddAllowBlockEntry(mailboxID, "block", "test@example.com"); err != nil { t.Fatal(err) } addr := startTestServer(t, backend) c, err := smtp.Dial(addr) if err != nil { t.Fatal(err) } defer c.Close() if err := c.Auth(smtp.PlainAuth("", "test@example.com", "testpass123", "127.0.0.1")); err != nil { t.Fatalf("auth: %v", err) } if err := c.Mail("test@example.com"); err != nil { t.Fatalf("MAIL FROM: %v", err) } err = c.Rcpt("inbox@example.com") if err == nil { t.Fatal("expected RCPT to be rejected for a blocked sender") } if !strings.Contains(err.Error(), "550") { t.Fatalf("expected 550, got: %v", err) } } // TestAllowListBypassesSpamQuarantine confirms a zero reject threshold quarantines a // non-allow-listed sender's mail into Spam (still accepted at SMTP level — spam is // stored for review, not silently bounced), while an allow-listed sender's mail // skips scoring entirely and lands in INBOX as normal. func TestAllowListBypassesSpamQuarantine(t *testing.T) { backend, mailboxID := newTestBackendWithMailbox(t) // Force every non-allow-listed message to be quarantined as spam. backend.Cfg.Section("Mailstore").Key("spam_reject_score").SetValue("0") send := func(t *testing.T) error { t.Helper() c, err := smtp.Dial(startTestServer(t, backend)) if err != nil { t.Fatal(err) } defer c.Close() if err := c.Auth(smtp.PlainAuth("", "test@example.com", "testpass123", "127.0.0.1")); err != nil { t.Fatalf("auth: %v", err) } if err := c.Mail("test@example.com"); err != nil { t.Fatalf("MAIL FROM: %v", err) } if err := c.Rcpt("inbox@example.com"); err != nil { t.Fatalf("RCPT: %v", err) } w, err := c.Data() if err != nil { t.Fatal(err) } w.Write([]byte("Subject: hi\r\n\r\nhi")) return w.Close() } if err := send(t); err != nil { t.Fatalf("expected delivery accepted (quarantined) with a zero reject threshold and no allow-list entry, got: %v", err) } spamMsgs, err := backend.DB.ListMessagesInFolder(mailboxID, "Spam") if err != nil || len(spamMsgs) != 1 { t.Fatalf("expected 1 quarantined message in Spam, got %d (err=%v)", len(spamMsgs), err) } if _, err := backend.DB.AddAllowBlockEntry(mailboxID, "allow", "test@example.com"); err != nil { t.Fatal(err) } if err := send(t); err != nil { t.Fatalf("expected delivery to succeed once the sender is allow-listed, got: %v", err) } inboxMsgs, err := backend.DB.ListMessagesInFolder(mailboxID, "INBOX") if err != nil || len(inboxMsgs) != 1 { t.Fatalf("expected 1 message in INBOX once allow-listed (spam scoring skipped), got %d (err=%v)", len(inboxMsgs), err) } } func TestFilterRuleDeleteDropsMessage(t *testing.T) { backend, mailboxID := newTestBackendWithMailbox(t) if _, err := backend.DB.CreateRule(mailboxID, 0, "subject", "contains", "drop-me", "delete", ""); err != nil { t.Fatal(err) } addr := startTestServer(t, backend) c, err := smtp.Dial(addr) if err != nil { t.Fatal(err) } defer c.Close() if err := c.Auth(smtp.PlainAuth("", "test@example.com", "testpass123", "127.0.0.1")); err != nil { t.Fatalf("auth: %v", err) } if err := c.Mail("test@example.com"); err != nil { t.Fatalf("MAIL FROM: %v", err) } if err := c.Rcpt("inbox@example.com"); err != nil { t.Fatalf("RCPT: %v", err) } w, err := c.Data() if err != nil { t.Fatal(err) } w.Write([]byte("Subject: drop-me please\r\n\r\nhi")) if err := w.Close(); err != nil { t.Fatalf("expected DATA to still report success even though the rule drops the message, got: %v", err) } msgs, err := backend.DB.ListMessagesInFolder(mailboxID, "INBOX") if err != nil { t.Fatal(err) } if len(msgs) != 0 { t.Fatalf("expected the delete rule to prevent storage, found %d messages", len(msgs)) } } func TestFilterRuleMarkReadSetsSeenFlag(t *testing.T) { backend, mailboxID := newTestBackendWithMailbox(t) if _, err := backend.DB.CreateRule(mailboxID, 0, "subject", "contains", "newsletter", "mark_read", ""); err != nil { t.Fatal(err) } addr := startTestServer(t, backend) c, err := smtp.Dial(addr) if err != nil { t.Fatal(err) } defer c.Close() if err := c.Auth(smtp.PlainAuth("", "test@example.com", "testpass123", "127.0.0.1")); err != nil { t.Fatalf("auth: %v", err) } if err := c.Mail("test@example.com"); err != nil { t.Fatalf("MAIL FROM: %v", err) } if err := c.Rcpt("inbox@example.com"); err != nil { t.Fatalf("RCPT: %v", err) } w, err := c.Data() if err != nil { t.Fatal(err) } w.Write([]byte("Subject: weekly newsletter\r\n\r\nhi")) if err := w.Close(); err != nil { t.Fatalf("DATA: %v", err) } msgs, err := backend.DB.ListMessagesInFolder(mailboxID, "INBOX") if err != nil { t.Fatal(err) } if len(msgs) != 1 { t.Fatalf("expected 1 message, got %d", len(msgs)) } if !strings.Contains(msgs[0].Flags, `\Seen`) { t.Fatalf("expected the mark_read rule to set \\Seen, got flags %q", msgs[0].Flags) } } func TestFilterRuleMoveToFolderStoresInNamedFolder(t *testing.T) { backend, mailboxID := newTestBackendWithMailbox(t) if _, err := backend.DB.CreateRule(mailboxID, 0, "subject", "contains", "spam", "move_to_folder", "Spam"); err != nil { t.Fatal(err) } addr := startTestServer(t, backend) c, err := smtp.Dial(addr) if err != nil { t.Fatal(err) } defer c.Close() if err := c.Auth(smtp.PlainAuth("", "test@example.com", "testpass123", "127.0.0.1")); err != nil { t.Fatalf("auth: %v", err) } if err := c.Mail("test@example.com"); err != nil { t.Fatalf("MAIL FROM: %v", err) } if err := c.Rcpt("inbox@example.com"); err != nil { t.Fatalf("RCPT: %v", err) } w, err := c.Data() if err != nil { t.Fatal(err) } w.Write([]byte("Subject: this looks like spam\r\n\r\nhi")) if err := w.Close(); err != nil { t.Fatalf("DATA: %v", err) } inbox, err := backend.DB.ListMessagesInFolder(mailboxID, "INBOX") if err != nil { t.Fatal(err) } if len(inbox) != 0 { t.Fatalf("expected nothing in INBOX, found %d", len(inbox)) } spam, err := backend.DB.ListMessagesInFolder(mailboxID, "Spam") if err != nil { t.Fatal(err) } if len(spam) != 1 { t.Fatalf("expected 1 message in Spam, got %d", len(spam)) } }