package smtp import ( "context" "strings" "testing" "time" ) func TestSendReturnsConfigurationErrorWhenSMTPMissing(t *testing.T) { mailer := NewSMTPMailer(SMTPConfig{Host: "", Port: 587, From: "noreply@example.com", Mode: SMTPModeTLS}) err := mailer.Send(context.Background(), "to@example.com", "subject", "

body

", "body") if err == nil { t.Fatal("expected configuration error") } if !strings.Contains(err.Error(), "smtp is not configured") { t.Fatalf("unexpected error: %v", err) } } func TestSendReturnsDeadlineExceededForExpiredContext(t *testing.T) { mailer := NewSMTPMailer(SMTPConfig{Host: "smtp.example.com", Port: 587, From: "noreply@example.com", Mode: SMTPModeTLS}) ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(-time.Second)) defer cancel() err := mailer.Send(ctx, "to@example.com", "subject", "

body

", "body") if err == nil { t.Fatal("expected context deadline exceeded") } if err != context.DeadlineExceeded { t.Fatalf("expected context deadline exceeded, got %v", err) } } func TestLoginAuthNextHandlesChallenges(t *testing.T) { auth := loginAuth{username: "alice", password: "s3cret"} proto, initial, err := auth.Start(nil) if err != nil { t.Fatalf("unexpected start error: %v", err) } if proto != "LOGIN" { t.Fatalf("expected LOGIN auth proto, got %q", proto) } if len(initial) != 0 { t.Fatalf("expected empty initial response, got %q", string(initial)) } value, err := auth.Next([]byte("Username:"), true) if err != nil { t.Fatalf("unexpected username challenge error: %v", err) } if string(value) != "alice" { t.Fatalf("expected username response alice, got %q", string(value)) } value, err = auth.Next([]byte("UGFzc3dvcmQ6"), true) if err != nil { t.Fatalf("unexpected password challenge error: %v", err) } if string(value) != "s3cret" { t.Fatalf("expected password response s3cret, got %q", string(value)) } } func TestLoginAuthNextHandlesTerminalAndUnexpectedChallenge(t *testing.T) { auth := loginAuth{username: "alice", password: "s3cret"} value, err := auth.Next([]byte("ignored"), false) if err != nil { t.Fatalf("expected nil error when more=false, got %v", err) } if value != nil { t.Fatalf("expected nil value when more=false, got %q", string(value)) } if _, err := auth.Next([]byte("realm"), true); err == nil { t.Fatal("expected error for unexpected login challenge") } } func TestBuildMIMEMessageContainsMultipartSections(t *testing.T) { msg := buildMIMEMessage("from@example.com", "to@example.com", "Subject", "plain body", "

html body

") checks := []string{ "From: from@example.com", "To: to@example.com", "Subject: Subject", "MIME-Version: 1.0", "Content-Type: text/plain; charset=UTF-8", "plain body", "Content-Type: text/html; charset=UTF-8", "

html body

", } for _, snippet := range checks { if !strings.Contains(msg, snippet) { t.Fatalf("expected MIME message to contain %q", snippet) } } if !strings.Contains(msg, "\r\n") { t.Fatal("expected CRLF separators in MIME message") } if !strings.Contains(msg, "Content-Type: multipart/alternative; boundary=mime-boundary-") { t.Fatalf("expected random mime boundary header, got %q", msg) } if !strings.Contains(msg, "--mime-boundary-") { t.Fatalf("expected mime boundary delimiters in message, got %q", msg) } } func TestDialHelpersReturnDeadlineExceededWhenDeadlineHasPassed(t *testing.T) { deadline := time.Now().Add(-time.Second) ctx := context.Background() if _, err := dialPlain(ctx, "smtp.example.com:25", "smtp.example.com", deadline); err != context.DeadlineExceeded { t.Fatalf("dialPlain expected context deadline exceeded, got %v", err) } if _, err := dialSSL(ctx, "smtp.example.com:465", "smtp.example.com", deadline); err != context.DeadlineExceeded { t.Fatalf("dialSSL expected context deadline exceeded, got %v", err) } if _, err := dialStartTLS(ctx, "smtp.example.com:587", "smtp.example.com", deadline); err != context.DeadlineExceeded { t.Fatalf("dialStartTLS expected context deadline exceeded, got %v", err) } }