Files
mailgoserver/internal/webui/webmail_contacts_test.go
T

98 lines
3.7 KiB
Go
Raw Normal View History

2026-08-15 21:49:25 +01:00
package webui
import (
"net/http"
"net/http/httptest"
"net/url"
"strconv"
"strings"
"testing"
)
// TestWebmailContactCreateEditDelete exercises the full self-service contact CRUD
// flow through the add/edit popup's shared save endpoint.
func TestWebmailContactCreateEditDelete(t *testing.T) {
app := newTestApp(t)
mux := app.Mux()
domains, _ := app.DB.ListDomains()
mailboxID := createTestMailboxWithPassword(t, app, "contactowner@example.com", domains[0].ID, "contact-password-1!")
cookie := webmailLoginSession(t, app, mailboxID)
save := func(id, email, name, phone string) *httptest.ResponseRecorder {
form := url.Values{"id": {id}, "email": {email}, "name": {name}, "phone": {phone}}
req := httptest.NewRequest(http.MethodPost, MailboxPrefix+"/contacts/save", strings.NewReader(form.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.AddCookie(cookie)
rec := httptest.NewRecorder()
mux.ServeHTTP(rec, req)
return rec
}
if rec := save("", "jane@example.com", "Jane Doe", "555-1234"); rec.Code != http.StatusFound {
t.Fatalf("create: status=%d body=%s", rec.Code, rec.Body.String())
}
contacts, err := app.DB.ListContacts(mailboxID)
if err != nil || len(contacts) != 1 || contacts[0].Name != "Jane Doe" {
t.Fatalf("expected 1 contact named Jane Doe, got %+v (err=%v)", contacts, err)
}
id := contacts[0].ID
if rec := save(strconv.FormatInt(id, 10), "jane@example.com", "Jane D.", ""); rec.Code != http.StatusFound {
t.Fatalf("edit: status=%d body=%s", rec.Code, rec.Body.String())
}
updated, err := app.DB.GetContactByID(mailboxID, id)
if err != nil || updated == nil || updated.Name != "Jane D." || updated.Phone != "" {
t.Fatalf("expected updated contact, got %+v (err=%v)", updated, err)
}
// Missing name/email is rejected rather than silently stored.
if rec := save("", "", "No Email", ""); rec.Code != http.StatusFound {
t.Fatalf("status=%d", rec.Code)
}
contacts, err = app.DB.ListContacts(mailboxID)
if err != nil || len(contacts) != 1 {
t.Fatalf("expected the invalid contact rejected (still 1), got %d (err=%v)", len(contacts), err)
}
delReq := httptest.NewRequest(http.MethodPost, MailboxPrefix+"/contacts/"+strconv.FormatInt(id, 10)+"/delete", nil)
delReq.AddCookie(cookie)
delRec := httptest.NewRecorder()
mux.ServeHTTP(delRec, delReq)
if delRec.Code != http.StatusFound {
t.Fatalf("delete: status=%d body=%s", delRec.Code, delRec.Body.String())
}
remaining, err := app.DB.ListContacts(mailboxID)
if err != nil || len(remaining) != 0 {
t.Fatalf("expected no contacts left, got %+v (err=%v)", remaining, err)
}
}
// TestWebmailContactScopedToOwnMailbox confirms one mailbox owner can't delete
// another mailbox's contact by guessing its ID.
func TestWebmailContactScopedToOwnMailbox(t *testing.T) {
app := newTestApp(t)
mux := app.Mux()
domains, _ := app.DB.ListDomains()
victimID := createTestMailboxWithPassword(t, app, "victim4@example.com", domains[0].ID, "victim-password-1!")
attackerID := createTestMailboxWithPassword(t, app, "attacker4@example.com", domains[0].ID, "attacker-password-1!")
contactID, err := app.DB.CreateContact(victimID, "friend@example.com", "Friend", "")
if err != nil {
t.Fatal(err)
}
attackerCookie := webmailLoginSession(t, app, attackerID)
req := httptest.NewRequest(http.MethodPost, MailboxPrefix+"/contacts/"+strconv.FormatInt(contactID, 10)+"/delete", nil)
req.AddCookie(attackerCookie)
rec := httptest.NewRecorder()
mux.ServeHTTP(rec, req)
if rec.Code != http.StatusFound {
t.Fatalf("status=%d", rec.Code)
}
stillThere, err := app.DB.GetContactByID(victimID, contactID)
if err != nil || stillThere == nil {
t.Fatalf("expected the victim's contact untouched, got %+v (err=%v)", stillThere, err)
}
}