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