add core lib

This commit is contained in:
2026-03-01 03:04:10 +01:00
parent 9a6818ea3c
commit baa764befd
22 changed files with 2353 additions and 0 deletions

View File

@@ -0,0 +1,126 @@
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", "<p>body</p>", "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", "<p>body</p>", "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", "<p>html body</p>")
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",
"<p>html body</p>",
}
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)
}
}