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