Files
warmbly/internal/tasks/partner_selection_test.go
T
Matthew Meszaros 43d24bc3d7 feat: customer-defined warmup routing rules on premium pool
new warmup_routing_rules table + repo + REST endpoints under /warmup/
routing. each rule matches a (sender, recipient) pair by domain, TLD,
provider bucket, or any wildcard, with a weight multiplier on the
selector. weight > 1 prefers the pairing, < 1 discourages, 0 excludes.
rules are evaluated in priority order (ascending) and combined with the
existing domain-diversity weighting. example use case: a customer can
say 'send Gmail-recipient warmup only from Google-classified senders'
with one rule; or 'never send to acme.com from this org's mailboxes'
with weight=0. premium pool only — free pool ignores rules.
2026-05-25 15:42:50 +00:00

130 lines
3.5 KiB
Go

package tasks
import (
"testing"
"github.com/google/uuid"
"github.com/warmbly/warmbly/internal/models"
)
func TestPickWeightedPartner_FallsBackToUniformWithoutDomains(t *testing.T) {
a := uuid.New()
b := uuid.New()
got := pickWeightedPartner([]uuid.UUID{a, b}, nil, nil, nil, "", nil)
if got != a && got != b {
t.Errorf("expected a or b, got %v", got)
}
}
func TestPickWeightedPartner_PrefersUnderRepresentedDomain(t *testing.T) {
saturatedID := uuid.New()
freshID := uuid.New()
domainsByID := map[uuid.UUID]string{
saturatedID: "gmail.com",
freshID: "fastmail.com",
}
domainCounts := map[string]int{
"gmail.com": 99, // very saturated
"fastmail.com": 0, // never used
}
freshHits := 0
iterations := 2000
for i := 0; i < iterations; i++ {
picked := pickWeightedPartner([]uuid.UUID{saturatedID, freshID}, domainsByID, domainCounts, nil, "", nil)
if picked == freshID {
freshHits++
}
}
// fresh weight = 1.0, saturated weight = 1/100 = 0.01.
// Expected fresh share ≈ 1.0 / 1.01 ≈ 99%.
if freshHits < int(float64(iterations)*0.9) {
t.Errorf("fresh domain should dominate selection; got %d/%d", freshHits, iterations)
}
}
func TestPickWeightedPartner_SingleCandidateReturnsIt(t *testing.T) {
id := uuid.New()
got := pickWeightedPartner([]uuid.UUID{id}, map[uuid.UUID]string{id: "x.com"}, map[string]int{"x.com": 5}, nil, "", nil)
if got != id {
t.Errorf("single candidate should be returned; got %v", got)
}
}
func TestPickWeightedPartner_RoutingRulePrefersProviderMatch(t *testing.T) {
googleRecipient := uuid.New()
microsoftRecipient := uuid.New()
domainsByID := map[uuid.UUID]string{
googleRecipient: "gmail.com",
microsoftRecipient: "outlook.com",
}
emailsByID := map[uuid.UUID]string{
googleRecipient: "g@gmail.com",
microsoftRecipient: "m@outlook.com",
}
rules := []models.WarmupRoutingRule{
{
Enabled: true,
Name: "google-to-google",
Priority: 1,
SenderMatchType: models.WarmupMatchProvider,
SenderMatchValue: "google",
RecipientMatchType: models.WarmupMatchProvider,
RecipientMatchValue: "google",
Weight: 10.0,
},
}
googleHits := 0
iterations := 2000
for i := 0; i < iterations; i++ {
picked := pickWeightedPartner(
[]uuid.UUID{googleRecipient, microsoftRecipient},
domainsByID, nil, rules, "sender@gmail.com", emailsByID,
)
if picked == googleRecipient {
googleHits++
}
}
// 10x preference should clearly dominate.
if googleHits < int(float64(iterations)*0.85) {
t.Errorf("routing rule should heavily favor Google→Google; got %d/%d", googleHits, iterations)
}
}
func TestPickWeightedPartner_RoutingRuleZeroWeightExcludes(t *testing.T) {
allowedID := uuid.New()
blockedID := uuid.New()
domainsByID := map[uuid.UUID]string{
allowedID: "good.com",
blockedID: "blocked.com",
}
emailsByID := map[uuid.UUID]string{
allowedID: "a@good.com",
blockedID: "b@blocked.com",
}
rules := []models.WarmupRoutingRule{
{
Enabled: true,
Name: "exclude-blocked",
Priority: 1,
SenderMatchType: models.WarmupMatchAny,
RecipientMatchType: models.WarmupMatchDomain,
RecipientMatchValue: "blocked.com",
Weight: 0,
},
}
for i := 0; i < 500; i++ {
picked := pickWeightedPartner(
[]uuid.UUID{allowedID, blockedID},
domainsByID, nil, rules, "sender@whatever.io", emailsByID,
)
if picked == blockedID {
t.Fatalf("weight=0 rule should exclude blocked partner")
}
}
}