package usecase import ( "bytes" "context" "errors" "testing" "github.com/google/uuid" "lambdamail/protocols/internal/application/port" "lambdamail/protocols/internal/domain/valueobject" ) type fakeTrusted struct { trusted map[string]bool err error } func (f *fakeTrusted) IsTrusted(_ context.Context, _ uuid.UUID, address string) (bool, error) { return f.trusted[address], f.err } func deliverAsSpam(t *testing.T, trusted port.TrustedSenders, auth port.InboundAuthResult, envelope string) string { t.Helper() repo := &recordingMessageRepository{} uc := NewProcessInboundEmailUseCase(nil, &capturingBlobStorage{}, repo) uc.SetScanner(&stubScanner{result: &valueobject.ScanResult{Verdict: valueobject.ScanVerdictSpamJunk}}) uc.SetAuthenticator(&stubAuthenticator{result: auth}) if trusted != nil { uc.SetTrustedSenders(trusted) } err := uc.Handle(context.Background(), ProcessInboundEmailInput{ Sender: envelope, Recipients: []port.MailboxRecord{{ID: uuid.New()}}, RecipientAddresses: []string{"me@example.test"}, Body: bytes.NewBufferString("From: Jenny \r\nSubject: re: hi\r\n\r\nbody"), }) if err != nil { t.Fatalf("Handle: %v", err) } return repo.persisted[0].TargetFolderName } var passDMARC = port.InboundAuthResult{SPF: "pass", DKIM: "pass", DMARC: "pass"} func TestTrustedSenderIsNotFiledAsSpam(t *testing.T) { trusted := &fakeTrusted{trusted: map[string]bool{"jenny@friend.test": true}} if got := deliverAsSpam(t, trusted, passDMARC, "jenny@friend.test"); got != "INBOX" { t.Fatalf("a trusted, authenticated sender was filed in %q", got) } } func TestUntrustedSenderStillGoesToJunk(t *testing.T) { trusted := &fakeTrusted{trusted: map[string]bool{"someone@else.test": true}} if got := deliverAsSpam(t, trusted, passDMARC, "jenny@friend.test"); got != "Junk" { t.Fatalf("an untrusted sender was filed in %q", got) } } // Writing a contact's address in From is all a spammer needs to do, so trust // only counts when the message proves it came from there. func TestForgedTrustedSenderStillGoesToJunk(t *testing.T) { trusted := &fakeTrusted{trusted: map[string]bool{"jenny@friend.test": true}} forged := port.InboundAuthResult{SPF: "fail", DKIM: "none", DMARC: "fail"} if got := deliverAsSpam(t, trusted, forged, "bounce@spammer.test"); got != "Junk" { t.Fatalf("a forged From reached the inbox: %q", got) } } // SPF that passed for the spammer's own domain proves nothing about From. func TestSPFForAnotherDomainDoesNotProveFrom(t *testing.T) { trusted := &fakeTrusted{trusted: map[string]bool{"jenny@friend.test": true}} other := port.InboundAuthResult{SPF: "pass", DKIM: "none", DMARC: "none"} if got := deliverAsSpam(t, trusted, other, "bounce@spammer.test"); got != "Junk" { t.Fatalf("SPF for a different domain let a forged From in: %q", got) } } // A domain with no DMARC record can still be proven by SPF for its own domain. func TestSPFForTheSameDomainProvesFrom(t *testing.T) { trusted := &fakeTrusted{trusted: map[string]bool{"jenny@friend.test": true}} spfOnly := port.InboundAuthResult{SPF: "pass", DKIM: "none", DMARC: "none"} if got := deliverAsSpam(t, trusted, spfOnly, "mailer@friend.test"); got != "INBOX" { t.Fatalf("SPF for the sender's own domain was not accepted: %q", got) } } func TestLookupFailureLeavesTheMessageInJunk(t *testing.T) { trusted := &fakeTrusted{trusted: map[string]bool{"jenny@friend.test": true}, err: errors.New("db down")} if got := deliverAsSpam(t, trusted, passDMARC, "jenny@friend.test"); got != "Junk" { t.Fatalf("a failed lookup let mail through: %q", got) } } func TestWithoutTrustedSendersSpamGoesToJunk(t *testing.T) { if got := deliverAsSpam(t, nil, passDMARC, "jenny@friend.test"); got != "Junk" { t.Fatalf("filed in %q", got) } }