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) } }