Files

158 lines
6.1 KiB
Go

package webui
import (
"net/http"
"net/http/httptest"
"net/url"
"strconv"
"strings"
"testing"
)
// TestWebmailRulesAddAndRemove confirms a mailbox owner can create a filter rule for
// their own mailbox through the self-service portal and remove it again — the same
// underlying CRUD the admin-side page already uses.
func TestWebmailRulesAddAndRemove(t *testing.T) {
app := newTestApp(t)
mux := app.Mux()
domains, _ := app.DB.ListDomains()
mailboxID := createTestMailboxWithPassword(t, app, "ruler@example.com", domains[0].ID, "ruler-password-1!")
cookie := webmailLoginSession(t, app, mailboxID)
form := "priority=0&condition_field=subject&condition_op=contains&condition_value=newsletter&action=move_to_folder&action_value=Newsletters"
req := httptest.NewRequest(http.MethodPost, MailboxPrefix+"/rules/add", strings.NewReader(form))
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("add rule: status=%d body=%s", rec.Code, rec.Body.String())
}
rules, err := app.DB.ListRulesForMailbox(mailboxID)
if err != nil || len(rules) != 1 {
t.Fatalf("expected 1 rule, got %d (err=%v)", len(rules), err)
}
if rules[0].ConditionValue != "newsletter" || rules[0].ActionValue != "Newsletters" {
t.Errorf("unexpected rule: %+v", rules[0])
}
// It actually takes effect at delivery time (reusing mailstore.ApplyRules,
// exercised in internal/smtpserver's own tests) — here just confirm the list page
// renders it and removal works.
listReq := httptest.NewRequest(http.MethodGet, MailboxPrefix+"/rules", nil)
listReq.AddCookie(cookie)
listRec := httptest.NewRecorder()
mux.ServeHTTP(listRec, listReq)
if listRec.Code != http.StatusOK || !strings.Contains(listRec.Body.String(), "Newsletters") {
t.Fatalf("expected the rule listed on the rules page, status=%d", listRec.Code)
}
rmReq := httptest.NewRequest(http.MethodPost, MailboxPrefix+"/rules/"+strconv.FormatInt(rules[0].ID, 10)+"/remove", nil)
rmReq.AddCookie(cookie)
rmRec := httptest.NewRecorder()
mux.ServeHTTP(rmRec, rmReq)
if rmRec.Code != http.StatusFound {
t.Fatalf("remove rule: status=%d", rmRec.Code)
}
remaining, err := app.DB.ListRulesForMailbox(mailboxID)
if err != nil || len(remaining) != 0 {
t.Fatalf("expected no rules left, got %d (err=%v)", len(remaining), err)
}
}
// TestWebmailRulesAddMultiCondition confirms the self-service rule builder's
// parallel condition_field/op/value arrays are correctly parsed into a
// multi-condition rule with the chosen match type.
func TestWebmailRulesAddMultiCondition(t *testing.T) {
app := newTestApp(t)
mux := app.Mux()
domains, _ := app.DB.ListDomains()
mailboxID := createTestMailboxWithPassword(t, app, "ruler3@example.com", domains[0].ID, "ruler-password-1!")
cookie := webmailLoginSession(t, app, mailboxID)
form := url.Values{
"priority": {"0"},
"match_type": {"any"},
"condition_field": {"from", "subject"},
"condition_op": {"contains", "contains"},
"condition_value": {"boss@work.example", "urgent"},
"action": {"mark_as_spam"},
"action_value": {""},
}
req := httptest.NewRequest(http.MethodPost, MailboxPrefix+"/rules/add", 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("add multi-condition rule: status=%d body=%s", rec.Code, rec.Body.String())
}
rules, err := app.DB.ListRulesForMailbox(mailboxID)
if err != nil || len(rules) != 1 {
t.Fatalf("expected 1 rule, got %d (err=%v)", len(rules), err)
}
conditions, matchType := rules[0].Conditions()
if matchType != "any" || len(conditions) != 2 {
t.Fatalf("expected 2 OR conditions, got matchType=%q conditions=%+v", matchType, conditions)
}
if rules[0].Action != "mark_as_spam" {
t.Fatalf("expected mark_as_spam action, got %q", rules[0].Action)
}
}
// TestWebmailRulesRejectsInvalidInput confirms a malformed rule submission is
// rejected rather than silently stored.
func TestWebmailRulesRejectsInvalidInput(t *testing.T) {
app := newTestApp(t)
mux := app.Mux()
domains, _ := app.DB.ListDomains()
mailboxID := createTestMailboxWithPassword(t, app, "ruler2@example.com", domains[0].ID, "ruler-password-1!")
cookie := webmailLoginSession(t, app, mailboxID)
// move_to_folder with no destination folder named.
form := "priority=0&condition_field=subject&condition_op=contains&condition_value=x&action=move_to_folder&action_value="
req := httptest.NewRequest(http.MethodPost, MailboxPrefix+"/rules/add", strings.NewReader(form))
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("status=%d", rec.Code)
}
rules, err := app.DB.ListRulesForMailbox(mailboxID)
if err != nil || len(rules) != 0 {
t.Fatalf("expected the invalid rule rejected, got %d rules (err=%v)", len(rules), err)
}
}
// TestWebmailRulesScopedToOwnMailbox confirms one mailbox owner can't remove another
// mailbox's rule by guessing its ID.
func TestWebmailRulesScopedToOwnMailbox(t *testing.T) {
app := newTestApp(t)
mux := app.Mux()
domains, _ := app.DB.ListDomains()
victimID := createTestMailboxWithPassword(t, app, "victim2@example.com", domains[0].ID, "victim-password-1!")
attackerID := createTestMailboxWithPassword(t, app, "attacker2@example.com", domains[0].ID, "attacker-password-1!")
ruleID, err := app.DB.CreateRule(victimID, 0, "subject", "contains", "x", "delete", "")
if err != nil {
t.Fatal(err)
}
attackerCookie := webmailLoginSession(t, app, attackerID)
req := httptest.NewRequest(http.MethodPost, MailboxPrefix+"/rules/"+strconv.FormatInt(ruleID, 10)+"/remove", 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.ListRulesForMailbox(victimID)
if err != nil || len(stillThere) != 1 {
t.Fatalf("expected the victim's rule untouched, got %d (err=%v)", len(stillThere), err)
}
}