102 lines
3.2 KiB
Go
102 lines
3.2 KiB
Go
package smtpserver
|
|
|
|
import (
|
|
"net/smtp"
|
|
"testing"
|
|
|
|
"mailgoserver/internal/db"
|
|
"mailgoserver/internal/mailstore"
|
|
)
|
|
|
|
// createTestMailboxWithQuota mirrors newTestBackendWithMailbox's inline mailbox setup,
|
|
// parameterized by quota so this file can create both a normal and an
|
|
// effectively-always-full mailbox.
|
|
func createTestMailboxWithQuota(t *testing.T, backend *Backend, store *mailstore.Store, email string, quotaBytes int64) int64 {
|
|
t.Helper()
|
|
dek := mailstore.GenerateDEK()
|
|
wrapped, nonce, err := store.WrapDEK(dek)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
hash, err := db.HashPassword("portal-password-unused")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
mailboxID, err := backend.DB.CreateMailbox(email, hash, 1, quotaBytes, wrapped, nonce)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return mailboxID
|
|
}
|
|
|
|
// TestPartialLocalDeliveryFailureBouncesAndAccepts confirms a multi-recipient
|
|
// transaction where one local mailbox accepts the message and another can't (quota
|
|
// exceeded, discovered only during DATA — RCPT can't catch it) is accepted (250, not
|
|
// 550 — the successful recipient already has it, so the client must not retry the
|
|
// whole transaction) and that the sender gets a bounce in their own mailbox describing
|
|
// the recipient that failed.
|
|
func TestPartialLocalDeliveryFailureBouncesAndAccepts(t *testing.T) {
|
|
backend, okMailboxID := newTestBackendWithMailbox(t)
|
|
store := backend.Mailstore
|
|
|
|
fullMailboxID := createTestMailboxWithQuota(t, backend, store, "full@example.com", 1)
|
|
senderMailboxID := createTestMailboxWithQuota(t, backend, store, "test@example.com", 5*1024*1024*1024)
|
|
|
|
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 (ok mailbox): %v", err)
|
|
}
|
|
if err := c.Rcpt("full@example.com"); err != nil {
|
|
t.Fatalf("RCPT (over-quota mailbox, still accepted at RCPT time): %v", err)
|
|
}
|
|
w, err := c.Data()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := w.Write([]byte("Subject: hello\r\n\r\nhi there")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := w.Close(); err != nil {
|
|
t.Fatalf("expected DATA to succeed (250, partial success) despite one recipient failing, got: %v", err)
|
|
}
|
|
|
|
okMsgs, err := backend.DB.ListMessagesInFolder(okMailboxID, "INBOX")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(okMsgs) != 1 {
|
|
t.Fatalf("expected the message delivered to inbox@example.com, got %d messages", len(okMsgs))
|
|
}
|
|
|
|
fullMsgs, err := backend.DB.ListMessagesInFolder(fullMailboxID, "INBOX")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(fullMsgs) != 0 {
|
|
t.Fatalf("expected no message delivered to the over-quota mailbox, got %d", len(fullMsgs))
|
|
}
|
|
|
|
bounces, err := backend.DB.ListMessagesInFolder(senderMailboxID, "INBOX")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(bounces) != 1 {
|
|
t.Fatalf("expected 1 bounce message in the sender's own mailbox, got %d", len(bounces))
|
|
}
|
|
if bounces[0].CachedSubject != "Undelivered Mail Returned to Sender" {
|
|
t.Errorf("bounce subject = %q", bounces[0].CachedSubject)
|
|
}
|
|
}
|