75 lines
2.5 KiB
Go
75 lines
2.5 KiB
Go
package webui
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"strings"
|
|
"testing"
|
|
|
|
"mailgoserver/internal/db"
|
|
"mailgoserver/internal/mailstore"
|
|
)
|
|
|
|
// TestWebmailComposeSendBouncesFailedRecipientToSenderInbox confirms that when one
|
|
// recipient in a multi-recipient send fails (here: an over-quota local mailbox, caught
|
|
// only at delivery time — RCPT-equivalent resolution succeeds), the sender still gets
|
|
// their flash "sent, but..." feedback AND a persistent bounce notification lands in
|
|
// their own INBOX, mirroring a real mail provider's delivery-failure notice.
|
|
func TestWebmailComposeSendBouncesFailedRecipientToSenderInbox(t *testing.T) {
|
|
app := newTestApp(t)
|
|
mux := app.Mux()
|
|
domains, _ := app.DB.ListDomains()
|
|
domainID := domains[0].ID
|
|
|
|
senderID := createTestMailboxWithPassword(t, app, "sender@example.com", domainID, "sender-password-1!")
|
|
|
|
// A second local mailbox with an effectively-zero quota, so StoreMessage always
|
|
// fails with ErrQuotaExceeded — a hermetic, deterministic delivery failure with no
|
|
// network dependency (unlike a relay-to-external-domain failure would be).
|
|
hash, err := db.HashPassword("full-password-1!")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
wrapped, nonce, err := app.Mailstore.WrapDEK(mailstore.GenerateDEK())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
fullID, err := app.DB.CreateMailbox("full@example.com", hash, domainID, 1, wrapped, nonce)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
cookie := webmailLoginSession(t, app, senderID)
|
|
form := url.Values{
|
|
"to": {"full@example.com"}, "subject": {"Big attachment incoming"}, "body_html": {"body text"},
|
|
}
|
|
req := httptest.NewRequest(http.MethodPost, MailboxPrefix+"/mail/compose", strings.NewReader(form.Encode()))
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
req.AddCookie(cookie)
|
|
rec := httptest.NewRecorder()
|
|
mux.ServeHTTP(rec, req)
|
|
if rec.Code != http.StatusFound {
|
|
t.Fatalf("compose send: status=%d body=%s", rec.Code, rec.Body.String())
|
|
}
|
|
|
|
fullMsgs, err := app.DB.ListMessagesInFolder(fullID, "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 := app.DB.ListMessagesInFolder(senderID, "INBOX")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(bounces) != 1 {
|
|
t.Fatalf("expected 1 bounce message in the sender's own INBOX, got %d", len(bounces))
|
|
}
|
|
if bounces[0].CachedSubject != "Undelivered Mail Returned to Sender" {
|
|
t.Errorf("bounce subject = %q", bounces[0].CachedSubject)
|
|
}
|
|
}
|