Files
mailgoserver/internal/mailstore/rules_test.go
T

210 lines
7.7 KiB
Go

package mailstore
import (
"testing"
"mailgoserver/internal/db"
)
// TestApplyRulesMultiConditionAnd confirms an "all" (AND) rule only matches when
// every condition matches.
func TestApplyRulesMultiConditionAnd(t *testing.T) {
s, mailboxID := newTestMailbox(t, 1024*1024)
conditions := []db.RuleCondition{
{Field: "to", Op: "contains", Value: "sales"},
{Field: "subject", Op: "contains", Value: "invoice"},
}
if _, err := s.DB.CreateRuleMulti(mailboxID, 0, conditions, "all", "", "move_to_folder", "Invoices", ""); err != nil {
t.Fatal(err)
}
// Matches "to" only — AND rule should not fire.
action, err := s.ApplyRules(mailboxID, map[string]string{"to": "sales@example.com", "subject": "hello"})
if err != nil {
t.Fatal(err)
}
if action.Folder != "" {
t.Fatalf("expected no match with only one AND condition satisfied, got folder=%q", action.Folder)
}
// Matches both — AND rule should fire.
action, err = s.ApplyRules(mailboxID, map[string]string{"to": "sales@example.com", "subject": "your invoice"})
if err != nil {
t.Fatal(err)
}
if action.Folder != "Invoices" {
t.Fatalf("expected move to Invoices when both AND conditions match, got %+v", action)
}
}
// TestApplyRulesMultiConditionOr confirms an "any" (OR) rule matches when at least
// one condition matches.
func TestApplyRulesMultiConditionOr(t *testing.T) {
s, mailboxID := newTestMailbox(t, 1024*1024)
conditions := []db.RuleCondition{
{Field: "from", Op: "contains", Value: "boss@work.example"},
{Field: "subject", Op: "contains", Value: "urgent"},
}
if _, err := s.DB.CreateRuleMulti(mailboxID, 0, conditions, "any", "", "mark_read", "", ""); err != nil {
t.Fatal(err)
}
action, err := s.ApplyRules(mailboxID, map[string]string{"from": "nobody@example.com", "subject": "urgent: read me"})
if err != nil {
t.Fatal(err)
}
if !action.MarkRead {
t.Fatalf("expected OR rule to fire on subject match alone, got %+v", action)
}
action, err = s.ApplyRules(mailboxID, map[string]string{"from": "nobody@example.com", "subject": "hello"})
if err != nil {
t.Fatal(err)
}
if action.MarkRead {
t.Fatalf("expected OR rule not to fire when neither condition matches, got %+v", action)
}
}
// TestApplyRulesMarkAsSpam confirms the mark_as_spam action routes into the Spam
// folder, same as score-based quarantine.
func TestApplyRulesMarkAsSpam(t *testing.T) {
s, mailboxID := newTestMailbox(t, 1024*1024)
conditions := []db.RuleCondition{{Field: "subject", Op: "contains", Value: "viagra"}}
if _, err := s.DB.CreateRuleMulti(mailboxID, 0, conditions, "all", "", "mark_as_spam", "", ""); err != nil {
t.Fatal(err)
}
action, err := s.ApplyRules(mailboxID, map[string]string{"subject": "cheap viagra now"})
if err != nil {
t.Fatal(err)
}
if action.Folder != "Junk" {
t.Fatalf("expected mark_as_spam to route into the Spam folder, got %+v", action)
}
}
// TestApplyRulesMultiValueConditionMatchesAny confirms a single condition holding
// several "\n"-joined values (the rule builder's chip input, e.g. several From
// addresses) matches if any one of them does.
func TestApplyRulesMultiValueConditionMatchesAny(t *testing.T) {
s, mailboxID := newTestMailbox(t, 1024*1024)
conditions := []db.RuleCondition{{Field: "from", Op: "contains", Value: "aaa@gogogo.com\nflower@monster.com"}}
if _, err := s.DB.CreateRuleMulti(mailboxID, 0, conditions, "all", "", "delete", "", ""); err != nil {
t.Fatal(err)
}
action, err := s.ApplyRules(mailboxID, map[string]string{"from": "flower@monster.com"})
if err != nil {
t.Fatal(err)
}
if !action.Drop {
t.Fatalf("expected match against the second of two chip values, got %+v", action)
}
action, err = s.ApplyRules(mailboxID, map[string]string{"from": "someone-else@example.com"})
if err != nil {
t.Fatal(err)
}
if action.Drop {
t.Fatalf("expected no match when neither chip value matches, got %+v", action)
}
}
// TestApplyRulesBodyAndHasAttachmentConditions confirms the two synthetic condition
// fields computed by the caller (smtpserver's deliverLocally, not real headers) match
// like any other field once present in the headers map.
func TestApplyRulesBodyAndHasAttachmentConditions(t *testing.T) {
s, mailboxID := newTestMailbox(t, 1024*1024)
conditions := []db.RuleCondition{
{Field: "body", Op: "contains", Value: "invoice"},
{Field: "has_attachment", Op: "equals", Value: "yes"},
}
if _, err := s.DB.CreateRuleMulti(mailboxID, 0, conditions, "all", "", "mark_as_spam", "", ""); err != nil {
t.Fatal(err)
}
action, err := s.ApplyRules(mailboxID, map[string]string{"body": "please pay this invoice", "has_attachment": "yes"})
if err != nil {
t.Fatal(err)
}
if action.Folder != "Junk" {
t.Fatalf("expected match when both body and has_attachment match, got %+v", action)
}
action, err = s.ApplyRules(mailboxID, map[string]string{"body": "please pay this invoice", "has_attachment": "no"})
if err != nil {
t.Fatal(err)
}
if action.Folder == "Junk" {
t.Fatalf("expected no match when has_attachment is no, got %+v", action)
}
}
// TestApplyRulesRecipientTypeCondition confirms recipient_type (the per-delivery
// to/cc/bcc-ness computed by the caller, mirroring Outlook's "I'm on the Cc line")
// only matches deliveries of that specific type.
func TestApplyRulesRecipientTypeCondition(t *testing.T) {
s, mailboxID := newTestMailbox(t, 1024*1024)
conditions := []db.RuleCondition{{Field: "recipient_type", Op: "equals", Value: "cc"}}
if _, err := s.DB.CreateRuleMulti(mailboxID, 0, conditions, "all", "", "mark_read", "", ""); err != nil {
t.Fatal(err)
}
action, err := s.ApplyRules(mailboxID, map[string]string{"recipient_type": "cc"})
if err != nil {
t.Fatal(err)
}
if !action.MarkRead {
t.Fatalf("expected match on cc delivery, got %+v", action)
}
action, err = s.ApplyRules(mailboxID, map[string]string{"recipient_type": "to"})
if err != nil {
t.Fatal(err)
}
if action.MarkRead {
t.Fatalf("expected no match on to delivery, got %+v", action)
}
}
// TestApplyRulesForwardActionReturnsKeepCopy confirms the forward action carries its
// destination address and keep_copy option through from ActionOptionsJSON.
func TestApplyRulesForwardActionReturnsKeepCopy(t *testing.T) {
s, mailboxID := newTestMailbox(t, 1024*1024)
conditions := []db.RuleCondition{{Field: "subject", Op: "contains", Value: "fwd"}}
if _, err := s.DB.CreateRuleMulti(mailboxID, 0, conditions, "all", "", "forward", "elsewhere@example.com", `{"keep_copy":false}`); err != nil {
t.Fatal(err)
}
action, err := s.ApplyRules(mailboxID, map[string]string{"subject": "fwd: hi"})
if err != nil {
t.Fatal(err)
}
if action.ForwardTo != "elsewhere@example.com" || action.KeepCopy {
t.Fatalf("expected forward to elsewhere@example.com with keep_copy=false, got %+v", action)
}
}
// TestApplyRulesLegacySingleConditionFallback confirms a rule row with an empty
// ConditionsJSON (as any rule created before multi-condition support existed would
// have) still evaluates correctly via the legacy condition_field/op/value columns.
func TestApplyRulesLegacySingleConditionFallback(t *testing.T) {
s, mailboxID := newTestMailbox(t, 1024*1024)
// CreateRule (not CreateRuleMulti) still writes conditions_json today, so to
// simulate genuinely old pre-migration data we insert directly with an empty
// conditions_json, exactly as an old row would look on disk.
if _, err := s.DB.Exec(`INSERT INTO esrv_mailbox_filter_rules (mailbox_id, priority, condition_field, condition_op, condition_value, action, action_value)
VALUES (?, 0, 'subject', 'contains', 'newsletter', 'delete', '')`, mailboxID); err != nil {
t.Fatal(err)
}
action, err := s.ApplyRules(mailboxID, map[string]string{"subject": "weekly newsletter"})
if err != nil {
t.Fatal(err)
}
if !action.Drop {
t.Fatalf("expected the legacy single-condition rule to still match, got %+v", action)
}
}