server_test.go raw

   1  package smtp
   2  
   3  import (
   4  	"context"
   5  	"fmt"
   6  	"net"
   7  	netsmtp "net/smtp"
   8  	"strings"
   9  	"sync"
  10  	"testing"
  11  	"time"
  12  )
  13  
  14  func TestServer_StartStop(t *testing.T) {
  15  	handler := func(email *InboundEmail) error { return nil }
  16  
  17  	cfg := DefaultServerConfig("test.example.com")
  18  	cfg.ListenAddr = "127.0.0.1:0" // random port
  19  
  20  	srv := NewServer(cfg, handler)
  21  	if err := srv.Start(); err != nil {
  22  		t.Fatalf("Start: %v", err)
  23  	}
  24  
  25  	addr := srv.Addr()
  26  	if addr == nil {
  27  		t.Fatal("Addr is nil after start")
  28  	}
  29  
  30  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  31  	defer cancel()
  32  	if err := srv.Stop(ctx); err != nil {
  33  		t.Fatalf("Stop: %v", err)
  34  	}
  35  }
  36  
  37  func TestServer_ReceiveEmail(t *testing.T) {
  38  	var mu sync.Mutex
  39  	var received *InboundEmail
  40  
  41  	handler := func(email *InboundEmail) error {
  42  		mu.Lock()
  43  		defer mu.Unlock()
  44  		received = email
  45  		return nil
  46  	}
  47  
  48  	cfg := DefaultServerConfig("test.example.com")
  49  	cfg.ListenAddr = "127.0.0.1:0"
  50  
  51  	srv := NewServer(cfg, handler)
  52  	if err := srv.Start(); err != nil {
  53  		t.Fatalf("Start: %v", err)
  54  	}
  55  	defer srv.Stop(context.Background())
  56  
  57  	addr := srv.Addr().(*net.TCPAddr)
  58  
  59  	// Send an email using net/smtp
  60  	c, err := netsmtp.Dial(fmt.Sprintf("127.0.0.1:%d", addr.Port))
  61  	if err != nil {
  62  		t.Fatalf("Dial: %v", err)
  63  	}
  64  	defer c.Quit()
  65  
  66  	if err := c.Mail("sender@external.com"); err != nil {
  67  		t.Fatalf("MAIL FROM: %v", err)
  68  	}
  69  	if err := c.Rcpt("npub1test@test.example.com"); err != nil {
  70  		t.Fatalf("RCPT TO: %v", err)
  71  	}
  72  
  73  	wc, err := c.Data()
  74  	if err != nil {
  75  		t.Fatalf("DATA: %v", err)
  76  	}
  77  
  78  	msg := "From: sender@external.com\r\nTo: npub1test@test.example.com\r\nSubject: Test\r\n\r\nHello from email!"
  79  	if _, err := wc.Write([]byte(msg)); err != nil {
  80  		t.Fatalf("Write: %v", err)
  81  	}
  82  	if err := wc.Close(); err != nil {
  83  		t.Fatalf("Close: %v", err)
  84  	}
  85  
  86  	// Give handler time to process
  87  	time.Sleep(100 * time.Millisecond)
  88  
  89  	mu.Lock()
  90  	defer mu.Unlock()
  91  
  92  	if received == nil {
  93  		t.Fatal("no email received")
  94  	}
  95  	if received.From != "sender@external.com" {
  96  		t.Errorf("From = %q", received.From)
  97  	}
  98  	if len(received.To) != 1 || received.To[0] != "npub1test@test.example.com" {
  99  		t.Errorf("To = %v", received.To)
 100  	}
 101  	if !strings.Contains(string(received.RawMessage), "Hello from email!") {
 102  		t.Error("message body not found in RawMessage")
 103  	}
 104  }
 105  
 106  func TestServer_RejectsWrongDomain(t *testing.T) {
 107  	handler := func(email *InboundEmail) error { return nil }
 108  
 109  	cfg := DefaultServerConfig("bridge.example.com")
 110  	cfg.ListenAddr = "127.0.0.1:0"
 111  
 112  	srv := NewServer(cfg, handler)
 113  	if err := srv.Start(); err != nil {
 114  		t.Fatalf("Start: %v", err)
 115  	}
 116  	defer srv.Stop(context.Background())
 117  
 118  	addr := srv.Addr().(*net.TCPAddr)
 119  
 120  	c, err := netsmtp.Dial(fmt.Sprintf("127.0.0.1:%d", addr.Port))
 121  	if err != nil {
 122  		t.Fatalf("Dial: %v", err)
 123  	}
 124  	defer c.Quit()
 125  
 126  	if err := c.Mail("sender@external.com"); err != nil {
 127  		t.Fatalf("MAIL FROM: %v", err)
 128  	}
 129  
 130  	// This should be rejected — wrong domain
 131  	err = c.Rcpt("user@wrong-domain.com")
 132  	if err == nil {
 133  		t.Error("expected rejection for wrong domain recipient")
 134  	}
 135  }
 136  
 137  func TestServer_StopWithoutStart(t *testing.T) {
 138  	handler := func(email *InboundEmail) error { return nil }
 139  	cfg := DefaultServerConfig("test.example.com")
 140  	srv := NewServer(cfg, handler)
 141  
 142  	// Stop without Start — server field is set but ln is nil
 143  	err := srv.Stop(context.Background())
 144  	if err != nil {
 145  		t.Fatalf("Stop without start should not error: %v", err)
 146  	}
 147  }
 148  
 149  func TestServer_AddrBeforeStart(t *testing.T) {
 150  	handler := func(email *InboundEmail) error { return nil }
 151  	cfg := DefaultServerConfig("test.example.com")
 152  	srv := NewServer(cfg, handler)
 153  
 154  	addr := srv.Addr()
 155  	if addr != nil {
 156  		t.Errorf("Addr before start should be nil, got %v", addr)
 157  	}
 158  }
 159  
 160  func TestServer_DefaultConfig(t *testing.T) {
 161  	cfg := DefaultServerConfig("example.com")
 162  	if cfg.Domain != "example.com" {
 163  		t.Errorf("Domain = %q", cfg.Domain)
 164  	}
 165  	if cfg.ListenAddr != "0.0.0.0:2525" {
 166  		t.Errorf("ListenAddr = %q", cfg.ListenAddr)
 167  	}
 168  	if cfg.MaxMessageBytes != 25*1024*1024 {
 169  		t.Errorf("MaxMessageBytes = %d", cfg.MaxMessageBytes)
 170  	}
 171  	if cfg.MaxRecipients != 10 {
 172  		t.Errorf("MaxRecipients = %d", cfg.MaxRecipients)
 173  	}
 174  }
 175  
 176  func TestServer_HandlerError(t *testing.T) {
 177  	handler := func(email *InboundEmail) error {
 178  		return fmt.Errorf("handler error")
 179  	}
 180  
 181  	cfg := DefaultServerConfig("test.example.com")
 182  	cfg.ListenAddr = "127.0.0.1:0"
 183  
 184  	srv := NewServer(cfg, handler)
 185  	if err := srv.Start(); err != nil {
 186  		t.Fatalf("Start: %v", err)
 187  	}
 188  	defer srv.Stop(context.Background())
 189  
 190  	addr := srv.Addr().(*net.TCPAddr)
 191  
 192  	c, err := netsmtp.Dial(fmt.Sprintf("127.0.0.1:%d", addr.Port))
 193  	if err != nil {
 194  		t.Fatalf("Dial: %v", err)
 195  	}
 196  	defer c.Quit()
 197  
 198  	c.Mail("sender@external.com")
 199  	c.Rcpt("npub1test@test.example.com")
 200  
 201  	wc, err := c.Data()
 202  	if err != nil {
 203  		t.Fatalf("DATA: %v", err)
 204  	}
 205  	wc.Write([]byte("From: sender@external.com\r\nTo: npub1test@test.example.com\r\n\r\nBody"))
 206  	err = wc.Close()
 207  	// The handler error should propagate as an SMTP error
 208  	if err == nil {
 209  		t.Error("expected error from handler failure")
 210  	}
 211  }
 212  
 213  func TestSession_RcptInvalidAddress(t *testing.T) {
 214  	s := &session{domain: "test.example.com", handler: func(e *InboundEmail) error { return nil }}
 215  	err := s.Rcpt("no-at-sign", nil)
 216  	if err == nil {
 217  		t.Error("expected error for address without @")
 218  	}
 219  }
 220  
 221  func TestSession_RcptEmptyLocalPart(t *testing.T) {
 222  	s := &session{domain: "test.example.com", handler: func(e *InboundEmail) error { return nil }}
 223  	err := s.Rcpt("@test.example.com", nil)
 224  	if err == nil {
 225  		t.Error("expected error for empty local part")
 226  	}
 227  }
 228  
 229  func TestSession_DataNoRecipients(t *testing.T) {
 230  	s := &session{domain: "test.example.com", handler: func(e *InboundEmail) error { return nil }}
 231  	err := s.Data(strings.NewReader("body"))
 232  	if err == nil {
 233  		t.Error("expected error for no recipients")
 234  	}
 235  }
 236  
 237  func TestSession_ResetAndLogout(t *testing.T) {
 238  	s := &session{
 239  		domain:  "test.example.com",
 240  		handler: func(e *InboundEmail) error { return nil },
 241  		from:    "test@example.com",
 242  		to:      []string{"rcpt@test.example.com"},
 243  	}
 244  
 245  	s.Reset()
 246  	if s.from != "" {
 247  		t.Errorf("from after reset = %q", s.from)
 248  	}
 249  	if s.to != nil {
 250  		t.Errorf("to after reset = %v", s.to)
 251  	}
 252  
 253  	err := s.Logout()
 254  	if err != nil {
 255  		t.Errorf("Logout error: %v", err)
 256  	}
 257  }
 258  
 259  func TestServer_StartListenError(t *testing.T) {
 260  	handler := func(email *InboundEmail) error { return nil }
 261  	cfg := DefaultServerConfig("test.example.com")
 262  	// Use an address that will fail to listen (already-bound or invalid)
 263  	cfg.ListenAddr = "999.999.999.999:99999"
 264  
 265  	srv := NewServer(cfg, handler)
 266  	err := srv.Start()
 267  	if err == nil {
 268  		srv.Stop(context.Background())
 269  		t.Fatal("expected error from invalid listen address")
 270  	}
 271  }
 272  
 273  func TestServer_CustomConfig(t *testing.T) {
 274  	handler := func(email *InboundEmail) error { return nil }
 275  	cfg := ServerConfig{
 276  		Domain:          "test.example.com",
 277  		ListenAddr:      "127.0.0.1:0",
 278  		MaxMessageBytes: 1024,
 279  		MaxRecipients:   5,
 280  		ReadTimeout:     30 * time.Second,
 281  		WriteTimeout:    30 * time.Second,
 282  	}
 283  
 284  	srv := NewServer(cfg, handler)
 285  	if err := srv.Start(); err != nil {
 286  		t.Fatalf("Start: %v", err)
 287  	}
 288  	defer srv.Stop(context.Background())
 289  
 290  	if srv.Addr() == nil {
 291  		t.Error("Addr should not be nil after Start")
 292  	}
 293  }
 294  
 295  func TestSession_DataReadError(t *testing.T) {
 296  	s := &session{
 297  		domain:  "test.example.com",
 298  		handler: func(e *InboundEmail) error { return nil },
 299  		to:      []string{"user@test.example.com"},
 300  	}
 301  
 302  	// errReader always returns an error
 303  	err := s.Data(&errReader{})
 304  	if err == nil {
 305  		t.Error("expected error from failing reader")
 306  	}
 307  	if !strings.Contains(err.Error(), "read message") {
 308  		t.Errorf("expected 'read message' error, got: %v", err)
 309  	}
 310  }
 311  
 312  type errReader struct{}
 313  
 314  func (e *errReader) Read(p []byte) (n int, err error) {
 315  	return 0, fmt.Errorf("read error")
 316  }
 317  
 318  func TestSession_MailStoresFrom(t *testing.T) {
 319  	s := &session{domain: "test.example.com"}
 320  	err := s.Mail("sender@example.com", nil)
 321  	if err != nil {
 322  		t.Fatalf("Mail error: %v", err)
 323  	}
 324  	if s.from != "sender@example.com" {
 325  		t.Errorf("from = %q, want sender@example.com", s.from)
 326  	}
 327  }
 328  
 329  func TestServer_StopReturnsShutdownError(t *testing.T) {
 330  	// Start a server, then stop it — should succeed
 331  	handler := func(email *InboundEmail) error { return nil }
 332  	cfg := DefaultServerConfig("test.example.com")
 333  	cfg.ListenAddr = "127.0.0.1:0"
 334  
 335  	srv := NewServer(cfg, handler)
 336  	if err := srv.Start(); err != nil {
 337  		t.Fatalf("Start: %v", err)
 338  	}
 339  
 340  	// Normal stop should succeed
 341  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
 342  	defer cancel()
 343  	err := srv.Stop(ctx)
 344  	if err != nil {
 345  		t.Errorf("Stop should succeed: %v", err)
 346  	}
 347  }
 348  
 349  func TestServer_MultipleRecipients(t *testing.T) {
 350  	var mu sync.Mutex
 351  	var received *InboundEmail
 352  
 353  	handler := func(email *InboundEmail) error {
 354  		mu.Lock()
 355  		defer mu.Unlock()
 356  		received = email
 357  		return nil
 358  	}
 359  
 360  	cfg := DefaultServerConfig("test.example.com")
 361  	cfg.ListenAddr = "127.0.0.1:0"
 362  
 363  	srv := NewServer(cfg, handler)
 364  	if err := srv.Start(); err != nil {
 365  		t.Fatalf("Start: %v", err)
 366  	}
 367  	defer srv.Stop(context.Background())
 368  
 369  	addr := srv.Addr().(*net.TCPAddr)
 370  
 371  	c, err := netsmtp.Dial(fmt.Sprintf("127.0.0.1:%d", addr.Port))
 372  	if err != nil {
 373  		t.Fatalf("Dial: %v", err)
 374  	}
 375  	defer c.Quit()
 376  
 377  	c.Mail("sender@external.com")
 378  	c.Rcpt("alice@test.example.com")
 379  	c.Rcpt("bob@test.example.com")
 380  
 381  	wc, _ := c.Data()
 382  	wc.Write([]byte("From: sender@external.com\r\nTo: alice@test.example.com, bob@test.example.com\r\nSubject: Multi\r\n\r\nMulti test"))
 383  	wc.Close()
 384  
 385  	time.Sleep(100 * time.Millisecond)
 386  
 387  	mu.Lock()
 388  	defer mu.Unlock()
 389  
 390  	if received == nil {
 391  		t.Fatal("no email received")
 392  	}
 393  	if len(received.To) != 2 {
 394  		t.Errorf("expected 2 recipients, got %d: %v", len(received.To), received.To)
 395  	}
 396  }
 397