|
1
|
# Testing Guide for POP3 Implementation |
|
2
|
|
|
3
|
This document provides comprehensive testing examples and strategies for POP3 protocol implementations. |
|
4
|
|
|
5
|
## Table of Contents |
|
6
|
|
|
7
|
1. [Unit Testing](#unit-testing) |
|
8
|
2. [Integration Testing](#integration-testing) |
|
9
|
3. [Mock Testing](#mock-testing) |
|
10
|
4. [Load Testing](#load-testing) |
|
11
|
5. [Security Testing](#security-testing) |
|
12
|
6. [Testing Best Practices](#testing-best-practices) |
|
13
|
|
|
14
|
## Unit Testing |
|
15
|
|
|
16
|
### Testing POP3 Command Parser |
|
17
|
|
|
18
|
```rust |
|
19
|
#[cfg(test)] |
|
20
|
mod command_parser_tests { |
|
21
|
use super::*; |
|
22
|
|
|
23
|
#[test] |
|
24
|
fn test_parse_user_command() { |
|
25
|
let cmd = "USER testuser"; |
|
26
|
let parsed = parse_command(cmd); |
|
27
|
assert_eq!(parsed.command, "USER"); |
|
28
|
assert_eq!(parsed.args, vec!["testuser"]); |
|
29
|
} |
|
30
|
|
|
31
|
#[test] |
|
32
|
fn test_parse_pass_command() { |
|
33
|
let cmd = "PASS secret123"; |
|
34
|
let parsed = parse_command(cmd); |
|
35
|
assert_eq!(parsed.command, "PASS"); |
|
36
|
assert_eq!(parsed.args, vec!["secret123"]); |
|
37
|
} |
|
38
|
|
|
39
|
#[test] |
|
40
|
fn test_parse_retr_command() { |
|
41
|
let cmd = "RETR 5"; |
|
42
|
let parsed = parse_command(cmd); |
|
43
|
assert_eq!(parsed.command, "RETR"); |
|
44
|
assert_eq!(parsed.args, vec!["5"]); |
|
45
|
} |
|
46
|
|
|
47
|
#[test] |
|
48
|
fn test_parse_command_case_insensitive() { |
|
49
|
let cmd = "user TestUser"; |
|
50
|
let parsed = parse_command(cmd); |
|
51
|
assert_eq!(parsed.command, "USER"); |
|
52
|
assert_eq!(parsed.args, vec!["TestUser"]); |
|
53
|
} |
|
54
|
|
|
55
|
#[test] |
|
56
|
fn test_parse_invalid_command() { |
|
57
|
let cmd = "INVALID"; |
|
58
|
let result = parse_command(cmd); |
|
59
|
assert!(result.is_err()); |
|
60
|
} |
|
61
|
} |
|
62
|
``` |
|
63
|
|
|
64
|
### Testing Authentication Logic |
|
65
|
|
|
66
|
```rust |
|
67
|
#[cfg(test)] |
|
68
|
mod auth_tests { |
|
69
|
use super::*; |
|
70
|
|
|
71
|
#[test] |
|
72
|
fn test_valid_authentication() { |
|
73
|
let authenticator = create_test_authenticator(); |
|
74
|
let result = authenticator.authenticate("testuser", "testpass"); |
|
75
|
assert!(result.is_ok()); |
|
76
|
assert_eq!(result.unwrap(), true); |
|
77
|
} |
|
78
|
|
|
79
|
#[test] |
|
80
|
fn test_invalid_password() { |
|
81
|
let authenticator = create_test_authenticator(); |
|
82
|
let result = authenticator.authenticate("testuser", "wrongpass"); |
|
83
|
assert!(result.is_ok()); |
|
84
|
assert_eq!(result.unwrap(), false); |
|
85
|
} |
|
86
|
|
|
87
|
#[test] |
|
88
|
fn test_nonexistent_user() { |
|
89
|
let authenticator = create_test_authenticator(); |
|
90
|
let result = authenticator.authenticate("nonexistent", "password"); |
|
91
|
assert!(result.is_ok()); |
|
92
|
assert_eq!(result.unwrap(), false); |
|
93
|
} |
|
94
|
|
|
95
|
#[test] |
|
96
|
fn test_empty_credentials() { |
|
97
|
let authenticator = create_test_authenticator(); |
|
98
|
let result = authenticator.authenticate("", ""); |
|
99
|
assert!(result.is_ok()); |
|
100
|
assert_eq!(result.unwrap(), false); |
|
101
|
} |
|
102
|
|
|
103
|
#[test] |
|
104
|
fn test_special_characters_in_password() { |
|
105
|
let authenticator = create_test_authenticator(); |
|
106
|
// Add user with special characters |
|
107
|
authenticator.add_user("special", "p@ss!w0rd#$%"); |
|
108
|
let result = authenticator.authenticate("special", "p@ss!w0rd#$%"); |
|
109
|
assert!(result.is_ok()); |
|
110
|
assert_eq!(result.unwrap(), true); |
|
111
|
} |
|
112
|
} |
|
113
|
``` |
|
114
|
|
|
115
|
### Testing Mailbox Operations |
|
116
|
|
|
117
|
```rust |
|
118
|
#[cfg(test)] |
|
119
|
mod mailbox_tests { |
|
120
|
use super::*; |
|
121
|
|
|
122
|
#[test] |
|
123
|
fn test_stat_empty_mailbox() { |
|
124
|
let mailbox = Mailbox::new(); |
|
125
|
let (count, size) = mailbox.stat(); |
|
126
|
assert_eq!(count, 0); |
|
127
|
assert_eq!(size, 0); |
|
128
|
} |
|
129
|
|
|
130
|
#[test] |
|
131
|
fn test_stat_with_messages() { |
|
132
|
let mut mailbox = Mailbox::new(); |
|
133
|
mailbox.add_message("Message 1", 100); |
|
134
|
mailbox.add_message("Message 2", 200); |
|
135
|
let (count, size) = mailbox.stat(); |
|
136
|
assert_eq!(count, 2); |
|
137
|
assert_eq!(size, 300); |
|
138
|
} |
|
139
|
|
|
140
|
#[test] |
|
141
|
fn test_list_messages() { |
|
142
|
let mut mailbox = Mailbox::new(); |
|
143
|
mailbox.add_message("Message 1", 100); |
|
144
|
mailbox.add_message("Message 2", 200); |
|
145
|
let list = mailbox.list(); |
|
146
|
assert_eq!(list.len(), 2); |
|
147
|
assert_eq!(list[0], (1, 100)); |
|
148
|
assert_eq!(list[1], (2, 200)); |
|
149
|
} |
|
150
|
|
|
151
|
#[test] |
|
152
|
fn test_retrieve_message() { |
|
153
|
let mut mailbox = Mailbox::new(); |
|
154
|
mailbox.add_message("Test Message Content", 100); |
|
155
|
let msg = mailbox.retrieve(1); |
|
156
|
assert!(msg.is_some()); |
|
157
|
assert_eq!(msg.unwrap(), "Test Message Content"); |
|
158
|
} |
|
159
|
|
|
160
|
#[test] |
|
161
|
fn test_retrieve_nonexistent_message() { |
|
162
|
let mailbox = Mailbox::new(); |
|
163
|
let msg = mailbox.retrieve(999); |
|
164
|
assert!(msg.is_none()); |
|
165
|
} |
|
166
|
|
|
167
|
#[test] |
|
168
|
fn test_delete_message() { |
|
169
|
let mut mailbox = Mailbox::new(); |
|
170
|
mailbox.add_message("Message", 100); |
|
171
|
let result = mailbox.delete(1); |
|
172
|
assert!(result.is_ok()); |
|
173
|
|
|
174
|
// Message should be marked deleted but still exist |
|
175
|
assert_eq!(mailbox.messages.len(), 1); |
|
176
|
assert_eq!(mailbox.messages[0].deleted, true); |
|
177
|
} |
|
178
|
|
|
179
|
#[test] |
|
180
|
fn test_reset_deletions() { |
|
181
|
let mut mailbox = Mailbox::new(); |
|
182
|
mailbox.add_message("Message", 100); |
|
183
|
mailbox.delete(1).unwrap(); |
|
184
|
mailbox.reset(); |
|
185
|
|
|
186
|
// Deletion mark should be removed |
|
187
|
assert_eq!(mailbox.messages[0].deleted, false); |
|
188
|
} |
|
189
|
|
|
190
|
#[test] |
|
191
|
fn test_commit_deletions() { |
|
192
|
let mut mailbox = Mailbox::new(); |
|
193
|
mailbox.add_message("Message 1", 100); |
|
194
|
mailbox.add_message("Message 2", 200); |
|
195
|
mailbox.delete(1).unwrap(); |
|
196
|
mailbox.commit(); |
|
197
|
|
|
198
|
// Message 1 should be permanently removed |
|
199
|
assert_eq!(mailbox.messages.len(), 1); |
|
200
|
assert_eq!(mailbox.retrieve(2).is_some(), true); |
|
201
|
} |
|
202
|
|
|
203
|
#[test] |
|
204
|
fn test_stat_after_delete() { |
|
205
|
let mut mailbox = Mailbox::new(); |
|
206
|
mailbox.add_message("Message 1", 100); |
|
207
|
mailbox.add_message("Message 2", 200); |
|
208
|
mailbox.delete(1).unwrap(); |
|
209
|
|
|
210
|
// STAT should not count deleted messages |
|
211
|
let (count, size) = mailbox.stat(); |
|
212
|
assert_eq!(count, 1); |
|
213
|
assert_eq!(size, 200); |
|
214
|
} |
|
215
|
} |
|
216
|
``` |
|
217
|
|
|
218
|
### Testing Response Generation |
|
219
|
|
|
220
|
```rust |
|
221
|
#[cfg(test)] |
|
222
|
mod response_tests { |
|
223
|
use super::*; |
|
224
|
|
|
225
|
#[test] |
|
226
|
fn test_ok_response() { |
|
227
|
let response = Response::ok("User accepted"); |
|
228
|
assert_eq!(response.to_string(), "+OK User accepted\r\n"); |
|
229
|
} |
|
230
|
|
|
231
|
#[test] |
|
232
|
fn test_err_response() { |
|
233
|
let response = Response::err("Invalid command"); |
|
234
|
assert_eq!(response.to_string(), "-ERR Invalid command\r\n"); |
|
235
|
} |
|
236
|
|
|
237
|
#[test] |
|
238
|
fn test_multiline_response() { |
|
239
|
let lines = vec!["1 100".to_string(), "2 200".to_string()]; |
|
240
|
let response = Response::multiline("+OK 2 messages", lines); |
|
241
|
let expected = "+OK 2 messages\r\n1 100\r\n2 200\r\n.\r\n"; |
|
242
|
assert_eq!(response.to_string(), expected); |
|
243
|
} |
|
244
|
|
|
245
|
#[test] |
|
246
|
fn test_stat_response() { |
|
247
|
let response = Response::stat(3, 600); |
|
248
|
assert_eq!(response.to_string(), "+OK 3 600\r\n"); |
|
249
|
} |
|
250
|
} |
|
251
|
``` |
|
252
|
|
|
253
|
## Integration Testing |
|
254
|
|
|
255
|
### End-to-End Session Test |
|
256
|
|
|
257
|
```rust |
|
258
|
#[tokio::test] |
|
259
|
async fn test_complete_pop3_session() { |
|
260
|
// Start test server |
|
261
|
let server = start_test_pop3_server().await; |
|
262
|
|
|
263
|
// Connect client |
|
264
|
let mut client = Pop3Client::connect("127.0.0.1", 11000).await.unwrap(); |
|
265
|
|
|
266
|
// Authenticate |
|
267
|
client.send("USER testuser").await.unwrap(); |
|
268
|
let resp = client.read_response().await.unwrap(); |
|
269
|
assert!(resp.starts_with("+OK")); |
|
270
|
|
|
271
|
client.send("PASS testpass").await.unwrap(); |
|
272
|
let resp = client.read_response().await.unwrap(); |
|
273
|
assert!(resp.starts_with("+OK")); |
|
274
|
|
|
275
|
// Get statistics |
|
276
|
client.send("STAT").await.unwrap(); |
|
277
|
let resp = client.read_response().await.unwrap(); |
|
278
|
assert!(resp.contains("+OK")); |
|
279
|
|
|
280
|
// List messages |
|
281
|
client.send("LIST").await.unwrap(); |
|
282
|
let resp = client.read_response().await.unwrap(); |
|
283
|
assert!(resp.starts_with("+OK")); |
|
284
|
|
|
285
|
// Retrieve message |
|
286
|
client.send("RETR 1").await.unwrap(); |
|
287
|
let resp = client.read_response().await.unwrap(); |
|
288
|
assert!(resp.starts_with("+OK")); |
|
289
|
|
|
290
|
// Delete message |
|
291
|
client.send("DELE 1").await.unwrap(); |
|
292
|
let resp = client.read_response().await.unwrap(); |
|
293
|
assert!(resp.starts_with("+OK")); |
|
294
|
|
|
295
|
// Quit |
|
296
|
client.send("QUIT").await.unwrap(); |
|
297
|
let resp = client.read_response().await.unwrap(); |
|
298
|
assert!(resp.starts_with("+OK")); |
|
299
|
|
|
300
|
server.stop().await; |
|
301
|
} |
|
302
|
``` |
|
303
|
|
|
304
|
### SMTP to POP3 Integration Test |
|
305
|
|
|
306
|
```rust |
|
307
|
#[tokio::test] |
|
308
|
async fn test_smtp_to_pop3_delivery() { |
|
309
|
// Start both servers |
|
310
|
let smtp_server = start_test_smtp_server().await; |
|
311
|
let pop3_server = start_test_pop3_server().await; |
|
312
|
|
|
313
|
// Send email via SMTP |
|
314
|
let mut smtp_client = SmtpClient::connect("127.0.0.1", 2525).await.unwrap(); |
|
315
|
smtp_client.send_mail( |
|
316
|
"sender@test.com", |
|
317
|
vec!["testuser@test.com"], |
|
318
|
"Subject: Test\r\n\r\nTest body" |
|
319
|
).await.unwrap(); |
|
320
|
|
|
321
|
// Wait for delivery |
|
322
|
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; |
|
323
|
|
|
324
|
// Check with POP3 |
|
325
|
let mut pop3_client = Pop3Client::connect("127.0.0.1", 110).await.unwrap(); |
|
326
|
pop3_client.authenticate("testuser", "testpass").await.unwrap(); |
|
327
|
|
|
328
|
let (count, _) = pop3_client.stat().await.unwrap(); |
|
329
|
assert!(count > 0, "No messages received"); |
|
330
|
|
|
331
|
let msg = pop3_client.retrieve(count).await.unwrap(); |
|
332
|
assert!(msg.contains("Test body")); |
|
333
|
|
|
334
|
smtp_server.stop().await; |
|
335
|
pop3_server.stop().await; |
|
336
|
} |
|
337
|
``` |
|
338
|
|
|
339
|
### Concurrent Connections Test |
|
340
|
|
|
341
|
```rust |
|
342
|
#[tokio::test] |
|
343
|
async fn test_concurrent_connections() { |
|
344
|
let server = start_test_pop3_server().await; |
|
345
|
|
|
346
|
let handles: Vec<_> = (0..10) |
|
347
|
.map(|i| { |
|
348
|
tokio::spawn(async move { |
|
349
|
let mut client = Pop3Client::connect("127.0.0.1", 11000).await.unwrap(); |
|
350
|
client.authenticate("testuser", "testpass").await.unwrap(); |
|
351
|
let (count, size) = client.stat().await.unwrap(); |
|
352
|
client.quit().await.unwrap(); |
|
353
|
(i, count, size) |
|
354
|
}) |
|
355
|
}) |
|
356
|
.collect(); |
|
357
|
|
|
358
|
for handle in handles { |
|
359
|
let (i, count, size) = handle.await.unwrap(); |
|
360
|
println!("Client {} completed: {} messages, {} bytes", i, count, size); |
|
361
|
} |
|
362
|
|
|
363
|
server.stop().await; |
|
364
|
} |
|
365
|
``` |
|
366
|
|
|
367
|
## Mock Testing |
|
368
|
|
|
369
|
### Using Mockall for Storage |
|
370
|
|
|
371
|
```rust |
|
372
|
use mockall::predicate::*; |
|
373
|
use mockall::*; |
|
374
|
|
|
375
|
#[automock] |
|
376
|
pub trait MessageStorage { |
|
377
|
fn get_message(&self, user: &str, msg_id: usize) -> Result<Vec<u8>, Error>; |
|
378
|
fn list_messages(&self, user: &str) -> Result<Vec<(usize, usize)>, Error>; |
|
379
|
fn delete_message(&self, user: &str, msg_id: usize) -> Result<(), Error>; |
|
380
|
} |
|
381
|
|
|
382
|
#[cfg(test)] |
|
383
|
mod mock_storage_tests { |
|
384
|
use super::*; |
|
385
|
|
|
386
|
#[test] |
|
387
|
fn test_retrieve_with_mock() { |
|
388
|
let mut mock_storage = MockMessageStorage::new(); |
|
389
|
|
|
390
|
mock_storage |
|
391
|
.expect_get_message() |
|
392
|
.with(eq("testuser"), eq(1)) |
|
393
|
.times(1) |
|
394
|
.returning(|_, _| Ok(b"Test message".to_vec())); |
|
395
|
|
|
396
|
let msg = mock_storage.get_message("testuser", 1).unwrap(); |
|
397
|
assert_eq!(msg, b"Test message"); |
|
398
|
} |
|
399
|
|
|
400
|
#[test] |
|
401
|
fn test_list_with_mock() { |
|
402
|
let mut mock_storage = MockMessageStorage::new(); |
|
403
|
|
|
404
|
mock_storage |
|
405
|
.expect_list_messages() |
|
406
|
.with(eq("testuser")) |
|
407
|
.times(1) |
|
408
|
.returning(|_| Ok(vec![(1, 100), (2, 200)])); |
|
409
|
|
|
410
|
let messages = mock_storage.list_messages("testuser").unwrap(); |
|
411
|
assert_eq!(messages.len(), 2); |
|
412
|
assert_eq!(messages[0], (1, 100)); |
|
413
|
} |
|
414
|
|
|
415
|
#[test] |
|
416
|
fn test_delete_with_mock() { |
|
417
|
let mut mock_storage = MockMessageStorage::new(); |
|
418
|
|
|
419
|
mock_storage |
|
420
|
.expect_delete_message() |
|
421
|
.with(eq("testuser"), eq(1)) |
|
422
|
.times(1) |
|
423
|
.returning(|_, _| Ok(())); |
|
424
|
|
|
425
|
let result = mock_storage.delete_message("testuser", 1); |
|
426
|
assert!(result.is_ok()); |
|
427
|
} |
|
428
|
} |
|
429
|
``` |
|
430
|
|
|
431
|
### Mock Authentication Backend |
|
432
|
|
|
433
|
```rust |
|
434
|
#[automock] |
|
435
|
pub trait Authenticator { |
|
436
|
fn authenticate(&self, username: &str, password: &str) -> Result<bool, Error>; |
|
437
|
} |
|
438
|
|
|
439
|
#[cfg(test)] |
|
440
|
mod mock_auth_tests { |
|
441
|
use super::*; |
|
442
|
|
|
443
|
#[test] |
|
444
|
fn test_successful_auth() { |
|
445
|
let mut mock_auth = MockAuthenticator::new(); |
|
446
|
|
|
447
|
mock_auth |
|
448
|
.expect_authenticate() |
|
449
|
.with(eq("testuser"), eq("testpass")) |
|
450
|
.times(1) |
|
451
|
.returning(|_, _| Ok(true)); |
|
452
|
|
|
453
|
let result = mock_auth.authenticate("testuser", "testpass").unwrap(); |
|
454
|
assert_eq!(result, true); |
|
455
|
} |
|
456
|
|
|
457
|
#[test] |
|
458
|
fn test_failed_auth() { |
|
459
|
let mut mock_auth = MockAuthenticator::new(); |
|
460
|
|
|
461
|
mock_auth |
|
462
|
.expect_authenticate() |
|
463
|
.with(eq("testuser"), eq("wrongpass")) |
|
464
|
.times(1) |
|
465
|
.returning(|_, _| Ok(false)); |
|
466
|
|
|
467
|
let result = mock_auth.authenticate("testuser", "wrongpass").unwrap(); |
|
468
|
assert_eq!(result, false); |
|
469
|
} |
|
470
|
} |
|
471
|
``` |
|
472
|
|
|
473
|
## Load Testing |
|
474
|
|
|
475
|
### Basic Load Test |
|
476
|
|
|
477
|
```rust |
|
478
|
#[tokio::test] |
|
479
|
#[ignore] // Run separately with --ignored flag |
|
480
|
async fn test_connection_load() { |
|
481
|
let server = start_test_pop3_server().await; |
|
482
|
let concurrent_clients = 100; |
|
483
|
|
|
484
|
let start = std::time::Instant::now(); |
|
485
|
|
|
486
|
let handles: Vec<_> = (0..concurrent_clients) |
|
487
|
.map(|_| { |
|
488
|
tokio::spawn(async { |
|
489
|
let mut client = Pop3Client::connect("127.0.0.1", 11000).await?; |
|
490
|
client.authenticate("testuser", "testpass").await?; |
|
491
|
client.stat().await?; |
|
492
|
client.quit().await?; |
|
493
|
Ok::<_, Box<dyn std::error::Error>>(()) |
|
494
|
}) |
|
495
|
}) |
|
496
|
.collect(); |
|
497
|
|
|
498
|
let mut success_count = 0; |
|
499
|
for handle in handles { |
|
500
|
if handle.await.unwrap().is_ok() { |
|
501
|
success_count += 1; |
|
502
|
} |
|
503
|
} |
|
504
|
|
|
505
|
let duration = start.elapsed(); |
|
506
|
println!("Completed {} connections in {:?}", success_count, duration); |
|
507
|
println!("Throughput: {:.2} connections/sec", |
|
508
|
success_count as f64 / duration.as_secs_f64()); |
|
509
|
|
|
510
|
assert_eq!(success_count, concurrent_clients); |
|
511
|
|
|
512
|
server.stop().await; |
|
513
|
} |
|
514
|
``` |
|
515
|
|
|
516
|
### Message Retrieval Load Test |
|
517
|
|
|
518
|
```rust |
|
519
|
#[tokio::test] |
|
520
|
#[ignore] |
|
521
|
async fn test_message_retrieval_load() { |
|
522
|
let server = start_test_pop3_server().await; |
|
523
|
let num_retrievals = 1000; |
|
524
|
|
|
525
|
let start = std::time::Instant::now(); |
|
526
|
|
|
527
|
let mut client = Pop3Client::connect("127.0.0.1", 11000).await.unwrap(); |
|
528
|
client.authenticate("testuser", "testpass").await.unwrap(); |
|
529
|
|
|
530
|
for i in 1..=num_retrievals { |
|
531
|
client.retrieve(1).await.unwrap(); |
|
532
|
if i % 100 == 0 { |
|
533
|
println!("Retrieved {} messages", i); |
|
534
|
} |
|
535
|
} |
|
536
|
|
|
537
|
client.quit().await.unwrap(); |
|
538
|
|
|
539
|
let duration = start.elapsed(); |
|
540
|
println!("Retrieved {} messages in {:?}", num_retrievals, duration); |
|
541
|
println!("Throughput: {:.2} retrievals/sec", |
|
542
|
num_retrievals as f64 / duration.as_secs_f64()); |
|
543
|
|
|
544
|
server.stop().await; |
|
545
|
} |
|
546
|
``` |
|
547
|
|
|
548
|
## Security Testing |
|
549
|
|
|
550
|
### Authentication Brute Force Protection Test |
|
551
|
|
|
552
|
```rust |
|
553
|
#[tokio::test] |
|
554
|
async fn test_rate_limiting() { |
|
555
|
let server = start_test_pop3_server().await; |
|
556
|
|
|
557
|
let mut client = Pop3Client::connect("127.0.0.1", 11000).await.unwrap(); |
|
558
|
|
|
559
|
// Try multiple failed authentications |
|
560
|
for i in 0..10 { |
|
561
|
let _ = client.send(format!("USER testuser{}", i)).await; |
|
562
|
let _ = client.read_response().await; |
|
563
|
let _ = client.send("PASS wrongpass").await; |
|
564
|
let resp = client.read_response().await.unwrap(); |
|
565
|
|
|
566
|
if i >= 3 { |
|
567
|
// After 3 attempts, should be rate limited |
|
568
|
assert!(resp.contains("too many") || resp.contains("rate limit")); |
|
569
|
} |
|
570
|
} |
|
571
|
|
|
572
|
server.stop().await; |
|
573
|
} |
|
574
|
``` |
|
575
|
|
|
576
|
### SQL Injection Test (if using database) |
|
577
|
|
|
578
|
```rust |
|
579
|
#[tokio::test] |
|
580
|
async fn test_sql_injection_protection() { |
|
581
|
let server = start_test_pop3_server().await; |
|
582
|
let mut client = Pop3Client::connect("127.0.0.1", 11000).await.unwrap(); |
|
583
|
|
|
584
|
// Try SQL injection in username |
|
585
|
client.send("USER admin' OR '1'='1").await.unwrap(); |
|
586
|
let _ = client.read_response().await; |
|
587
|
|
|
588
|
client.send("PASS password").await.unwrap(); |
|
589
|
let resp = client.read_response().await.unwrap(); |
|
590
|
|
|
591
|
// Should fail authentication |
|
592
|
assert!(resp.starts_with("-ERR")); |
|
593
|
|
|
594
|
server.stop().await; |
|
595
|
} |
|
596
|
``` |
|
597
|
|
|
598
|
### Command Injection Test |
|
599
|
|
|
600
|
```rust |
|
601
|
#[tokio::test] |
|
602
|
async fn test_command_injection() { |
|
603
|
let server = start_test_pop3_server().await; |
|
604
|
let mut client = Pop3Client::connect("127.0.0.1", 11000).await.unwrap(); |
|
605
|
|
|
606
|
// Try command injection |
|
607
|
let malicious_commands = vec![ |
|
608
|
"USER test\r\nSTAT\r\n", |
|
609
|
"PASS test; rm -rf /", |
|
610
|
"RETR 1; cat /etc/passwd", |
|
611
|
]; |
|
612
|
|
|
613
|
for cmd in malicious_commands { |
|
614
|
client.send(cmd).await.unwrap(); |
|
615
|
let resp = client.read_response().await.unwrap(); |
|
616
|
// Should either fail or only execute intended command |
|
617
|
assert!(resp.starts_with("+OK") || resp.starts_with("-ERR")); |
|
618
|
} |
|
619
|
|
|
620
|
server.stop().await; |
|
621
|
} |
|
622
|
``` |
|
623
|
|
|
624
|
## Testing Best Practices |
|
625
|
|
|
626
|
### Test Organization |
|
627
|
|
|
628
|
```rust |
|
629
|
// Organize tests in modules |
|
630
|
#[cfg(test)] |
|
631
|
mod tests { |
|
632
|
mod unit { |
|
633
|
mod authentication { /* auth tests */ } |
|
634
|
mod mailbox { /* mailbox tests */ } |
|
635
|
mod commands { /* command tests */ } |
|
636
|
} |
|
637
|
|
|
638
|
mod integration { |
|
639
|
mod sessions { /* session tests */ } |
|
640
|
mod protocols { /* protocol tests */ } |
|
641
|
} |
|
642
|
|
|
643
|
mod security { |
|
644
|
mod injection { /* injection tests */ } |
|
645
|
mod rate_limit { /* rate limit tests */ } |
|
646
|
} |
|
647
|
} |
|
648
|
``` |
|
649
|
|
|
650
|
### Test Fixtures |
|
651
|
|
|
652
|
```rust |
|
653
|
// Create reusable test fixtures |
|
654
|
fn create_test_mailbox() -> Mailbox { |
|
655
|
let mut mailbox = Mailbox::new(); |
|
656
|
mailbox.add_message("Message 1", 100); |
|
657
|
mailbox.add_message("Message 2", 200); |
|
658
|
mailbox |
|
659
|
} |
|
660
|
|
|
661
|
fn create_test_authenticator() -> FileAuthenticator { |
|
662
|
let mut auth = FileAuthenticator::new(); |
|
663
|
auth.add_user("testuser", "testpass"); |
|
664
|
auth.add_user("alice", "password123"); |
|
665
|
auth |
|
666
|
} |
|
667
|
|
|
668
|
#[cfg(test)] |
|
669
|
mod tests { |
|
670
|
use super::*; |
|
671
|
|
|
672
|
#[test] |
|
673
|
fn test_with_fixture() { |
|
674
|
let mailbox = create_test_mailbox(); |
|
675
|
assert_eq!(mailbox.messages.len(), 2); |
|
676
|
} |
|
677
|
} |
|
678
|
``` |
|
679
|
|
|
680
|
### Property-Based Testing |
|
681
|
|
|
682
|
```rust |
|
683
|
use proptest::prelude::*; |
|
684
|
|
|
685
|
proptest! { |
|
686
|
#[test] |
|
687
|
fn test_message_id_always_positive(msg_id in 1usize..10000) { |
|
688
|
let mut mailbox = Mailbox::new(); |
|
689
|
mailbox.add_message("Test", 100); |
|
690
|
|
|
691
|
let result = mailbox.retrieve(msg_id); |
|
692
|
// Should not panic |
|
693
|
prop_assert!(result.is_some() || result.is_none()); |
|
694
|
} |
|
695
|
|
|
696
|
#[test] |
|
697
|
fn test_stat_consistency( |
|
698
|
msg_count in 0usize..100, |
|
699
|
msg_size in 1usize..10000 |
|
700
|
) { |
|
701
|
let mut mailbox = Mailbox::new(); |
|
702
|
|
|
703
|
for _ in 0..msg_count { |
|
704
|
mailbox.add_message("Test", msg_size); |
|
705
|
} |
|
706
|
|
|
707
|
let (count, size) = mailbox.stat(); |
|
708
|
prop_assert_eq!(count, msg_count); |
|
709
|
prop_assert_eq!(size, msg_count * msg_size); |
|
710
|
} |
|
711
|
} |
|
712
|
``` |
|
713
|
|
|
714
|
### Test Coverage |
|
715
|
|
|
716
|
```bash |
|
717
|
# Install tarpaulin for coverage |
|
718
|
cargo install cargo-tarpaulin |
|
719
|
|
|
720
|
# Run tests with coverage |
|
721
|
cargo tarpaulin --out Html --output-dir coverage/ |
|
722
|
|
|
723
|
# View coverage report |
|
724
|
open coverage/index.html |
|
725
|
``` |
|
726
|
|
|
727
|
### Continuous Integration |
|
728
|
|
|
729
|
```yaml |
|
730
|
# .github/workflows/test.yml |
|
731
|
name: Tests |
|
732
|
|
|
733
|
on: [push, pull_request] |
|
734
|
|
|
735
|
jobs: |
|
736
|
test: |
|
737
|
runs-on: ubuntu-latest |
|
738
|
steps: |
|
739
|
- uses: actions/checkout@v2 |
|
740
|
- uses: actions-rs/toolchain@v1 |
|
741
|
with: |
|
742
|
toolchain: stable |
|
743
|
- name: Run tests |
|
744
|
run: cargo test --all-features |
|
745
|
- name: Run integration tests |
|
746
|
run: cargo test --test '*' -- --ignored |
|
747
|
- name: Check code coverage |
|
748
|
run: | |
|
749
|
cargo install cargo-tarpaulin |
|
750
|
cargo tarpaulin --out Xml |
|
751
|
- name: Upload coverage |
|
752
|
uses: codecov/codecov-action@v1 |
|
753
|
``` |
|
754
|
|
|
755
|
## Debugging Tests |
|
756
|
|
|
757
|
### Enable Logging in Tests |
|
758
|
|
|
759
|
```rust |
|
760
|
#[cfg(test)] |
|
761
|
mod tests { |
|
762
|
use super::*; |
|
763
|
|
|
764
|
fn init_logger() { |
|
765
|
let _ = tracing_subscriber::fmt() |
|
766
|
.with_test_writer() |
|
767
|
.with_max_level(tracing::Level::DEBUG) |
|
768
|
.try_init(); |
|
769
|
} |
|
770
|
|
|
771
|
#[test] |
|
772
|
fn test_with_logging() { |
|
773
|
init_logger(); |
|
774
|
|
|
775
|
// Test code here |
|
776
|
tracing::debug!("Debug message in test"); |
|
777
|
} |
|
778
|
} |
|
779
|
``` |
|
780
|
|
|
781
|
### Using Test Output |
|
782
|
|
|
783
|
```bash |
|
784
|
# Show test output |
|
785
|
cargo test -- --nocapture |
|
786
|
|
|
787
|
# Show specific test output |
|
788
|
cargo test test_name -- --nocapture --exact |
|
789
|
|
|
790
|
# Run tests in sequence (not parallel) |
|
791
|
cargo test -- --test-threads=1 |
|
792
|
``` |
|
793
|
|
|
794
|
## Conclusion |
|
795
|
|
|
796
|
This testing guide provides a comprehensive framework for testing POP3 implementations. Combine unit tests, integration tests, mock tests, and security tests to ensure robust and reliable code. |
|
797
|
|
|
798
|
Key takeaways: |
|
799
|
- Test at multiple levels (unit, integration, system) |
|
800
|
- Use mocks for external dependencies |
|
801
|
- Include security tests |
|
802
|
- Measure and improve coverage |
|
803
|
- Automate testing in CI/CD |
|
804
|
- Document test scenarios and expectations |