Files
warmbly/internal/pkg/listquality/listquality_test.go
T
Matthew Meszaros 8ef6ee5545 feat: measure an uploaded list at import, and say what it looks like (#236)
* feat: refuse a launch whose list is known to be largely undeliverable

* feat: count the deliverable audience in SQL instead of subtracting two overlapping totals, surface the unverified advice, and stop a query error skipping the gate silently: a contact can be both suppressed and unsubscribed, so Total minus both removed it twice and inflated every share computed against the remainder, which for a fully overlapping list produced a negative deliverable clamped to zero; the unverified-list branch set a summary and remediation but neither Block nor Warn, and a preflight report only surfaces checks that did not pass, so that advice could never be displayed; and a failed audience query was treated as a pass, which is fail-open on a safety gate without even a log line saying the check did not run

* feat: measure an uploaded list at import time and report what it looks like

* feat: document the import assessment and show it in the wizard

* feat: count only sendable leads as invalid, and stop guessing which column held a malformed address: an invalid lead that was also suppressed sat in the numerator while Deliverable excluded it from the denominator, so a campaign whose sendable list was clean could project above 100% and be refused; every verification count now shares the deliverable predicate, and a row whose MAPPED address will not parse is recorded as malformed directly rather than scanning other cells for an at sign, which could pick up a notes field
2026-08-28 11:50:48 -07:00

78 lines
2.3 KiB
Go

package listquality
import "testing"
func repeat(email string, n int) []string {
out := make([]string, 0, n)
for i := 0; i < n; i++ {
out = append(out, email)
}
return out
}
// The case that decides whether this is usable: an ordinary business list must
// pass silently.
func TestAssessOrdinaryListIsQuiet(t *testing.T) {
emails := append(repeat("ada@acme.com", 50), repeat("someone@gmail.com", 50)...)
s := Assess(emails)
if s.Flagged {
t.Errorf("an ordinary list was flagged: %+v", s)
}
if s.BadSharePct != 0 {
t.Errorf("bad share = %.1f%%, want 0", s.BadSharePct)
}
}
func TestAssessFlagsAScrapedList(t *testing.T) {
emails := append(repeat("not-an-email", 30), repeat("x@mailinator.com", 30)...)
emails = append(emails, repeat("ada@acme.com", 40)...)
s := Assess(emails)
if !s.Flagged {
t.Errorf("a 60%% unusable list was not flagged: %+v", s)
}
if s.Malformed != 30 || s.Disposable != 30 {
t.Errorf("malformed = %d, disposable = %d, want 30 each", s.Malformed, s.Disposable)
}
if s.Summary == "" {
t.Error("a flagged list must say what is wrong with it")
}
}
// Role addresses are a choice, not a defect. Plenty of legitimate B2B lists are
// mostly info@ and sales@, and flagging those would be wrong.
func TestAssessDoesNotCountRoleAddressesAsBad(t *testing.T) {
s := Assess(repeat("info@acme.com", 100))
if s.Flagged {
t.Errorf("an all-role list was flagged as unusable: %+v", s)
}
if s.Role != 100 {
t.Errorf("role = %d, want 100 counted but not penalised", s.Role)
}
if s.BadSharePct != 0 {
t.Errorf("bad share = %.1f%%, want 0", s.BadSharePct)
}
}
func TestAssessIgnoresASampleTooSmallToJudge(t *testing.T) {
for _, n := range []int{1, 5, MinSample - 1} {
if s := Assess(repeat("not-an-email", n)); s.Flagged {
t.Errorf("a list of %d was flagged; too small to judge", n)
}
}
if s := Assess(repeat("not-an-email", MinSample)); !s.Flagged {
t.Errorf("a fully malformed list of %d should flag", MinSample)
}
}
func TestAssessHandlesEmptyAndBlank(t *testing.T) {
if s := Assess(nil); s.Flagged || s.Total != 0 {
t.Errorf("empty input: %+v", s)
}
// Blank rows are skipped rather than counted as malformed, or a file with
// trailing newlines would look like a bad list.
s := Assess([]string{"ada@acme.com", "", " "})
if s.Malformed != 0 {
t.Errorf("blank rows counted as malformed: %+v", s)
}
}