Add comprehensive testing guide and usage scenarios for POP3

Co-authored-by: setoelkahfi <1797197+setoelkahfi@users.noreply.github.com>

copilot-swe-agent[bot] committed Oct 13, 2025 at 17:23 UTC 761eb1ae79214179f52547aa0f6ed05c07338d36
2 files changed +1620
docs/SCENARIOS.md new
+816
@@ -0,0 +1,816 @@
1 +# POP3 Usage Scenarios and Workflows
2 +
3 +This document provides practical, real-world scenarios demonstrating how to use the POP3 implementation in various situations.
4 +
5 +## Table of Contents
6 +
7 +1. [Basic Email Retrieval](#basic-email-retrieval)
8 +2. [Download and Delete Workflow](#download-and-delete-workflow)
9 +3. [Selective Message Management](#selective-message-management)
10 +4. [Mobile Email Client](#mobile-email-client)
11 +5. [Email Backup Solution](#email-backup-solution)
12 +6. [Multi-Device Synchronization](#multi-device-synchronization)
13 +7. [Email Migration](#email-migration)
14 +8. [Automated Email Processing](#automated-email-processing)
15 +9. [Error Recovery Scenarios](#error-recovery-scenarios)
16 +
17 +## Basic Email Retrieval
18 +
19 +### Scenario: Simple Email Client
20 +
21 +A user wants to retrieve and read their emails using a simple email client.
22 +
23 +```rust
24 +use std::io::{BufRead, BufReader, Write};
25 +use std::net::TcpStream;
26 +
27 +fn retrieve_emails(username: &str, password: &str) -> Result<Vec<String>, Box<dyn std::error::Error>> {
28 + // Connect to server
29 + let mut stream = TcpStream::connect("mail.example.com:110")?;
30 + let mut reader = BufReader::new(stream.try_clone()?);
31 +
32 + // Read greeting
33 + let mut line = String::new();
34 + reader.read_line(&mut line)?;
35 + println!("Server: {}", line.trim());
36 +
37 + // Authenticate
38 + writeln!(stream, "USER {}", username)?;
39 + line.clear();
40 + reader.read_line(&mut line)?;
41 +
42 + writeln!(stream, "PASS {}", password)?;
43 + line.clear();
44 + reader.read_line(&mut line)?;
45 +
46 + if !line.starts_with("+OK") {
47 + return Err("Authentication failed".into());
48 + }
49 +
50 + // Get message count
51 + writeln!(stream, "STAT")?;
52 + line.clear();
53 + reader.read_line(&mut line)?;
54 +
55 + let parts: Vec<&str> = line.split_whitespace().collect();
56 + let message_count: usize = parts.get(1).and_then(|s| s.parse().ok()).unwrap_or(0);
57 +
58 + println!("You have {} message(s)", message_count);
59 +
60 + // Retrieve all messages
61 + let mut messages = Vec::new();
62 +
63 + for i in 1..=message_count {
64 + writeln!(stream, "RETR {}", i)?;
65 + line.clear();
66 + reader.read_line(&mut line)?;
67 +
68 + if line.starts_with("+OK") {
69 + let mut message = String::new();
70 + loop {
71 + line.clear();
72 + reader.read_line(&mut line)?;
73 + if line.trim() == "." {
74 + break;
75 + }
76 + message.push_str(&line);
77 + }
78 + messages.push(message);
79 + }
80 + }
81 +
82 + // Close connection
83 + writeln!(stream, "QUIT")?;
84 +
85 + Ok(messages)
86 +}
87 +
88 +fn main() {
89 + match retrieve_emails("user@example.com", "password") {
90 + Ok(messages) => {
91 + for (i, msg) in messages.iter().enumerate() {
92 + println!("\n=== Message {} ===", i + 1);
93 + // Parse and display message
94 + display_message(msg);
95 + }
96 + }
97 + Err(e) => eprintln!("Error: {}", e),
98 + }
99 +}
100 +
101 +fn display_message(message: &str) {
102 + // Parse headers
103 + let mut in_headers = true;
104 + for line in message.lines() {
105 + if line.is_empty() {
106 + in_headers = false;
107 + continue;
108 + }
109 +
110 + if in_headers {
111 + if line.starts_with("From:") || line.starts_with("Subject:") || line.starts_with("Date:") {
112 + println!("{}", line);
113 + }
114 + } else {
115 + println!("{}", line);
116 + }
117 + }
118 +}
119 +```
120 +
121 +## Download and Delete Workflow
122 +
123 +### Scenario: Traditional POP3 Client
124 +
125 +User wants to download emails to their local device and remove them from the server.
126 +
127 +```rust
128 +fn download_and_delete_emails(username: &str, password: &str, save_dir: &str)
129 + -> Result<(), Box<dyn std::error::Error>>
130 +{
131 + let mut client = Pop3Client::connect("mail.example.com", 110)?;
132 + client.authenticate(username, password)?;
133 +
134 + let (message_count, _) = client.stat()?;
135 + println!("Downloading {} message(s)...", message_count);
136 +
137 + for i in 1..=message_count {
138 + // Retrieve message
139 + let message = client.retrieve(i)?;
140 +
141 + // Save to file
142 + let filename = format!("{}/message_{}.eml", save_dir, i);
143 + std::fs::write(&filename, message)?;
144 + println!("Saved message {} to {}", i, filename);
145 +
146 + // Delete from server
147 + client.delete(i)?;
148 + println!("Deleted message {} from server", i);
149 + }
150 +
151 + // Commit deletions and close
152 + client.quit()?;
153 +
154 + println!("Download complete. {} messages saved and deleted.", message_count);
155 +
156 + Ok(())
157 +}
158 +
159 +fn main() {
160 + let save_dir = "./emails";
161 + std::fs::create_dir_all(save_dir).unwrap();
162 +
163 + if let Err(e) = download_and_delete_emails("user@example.com", "password", save_dir) {
164 + eprintln!("Error: {}", e);
165 + }
166 +}
167 +```
168 +
169 +## Selective Message Management
170 +
171 +### Scenario: Download Only Specific Messages
172 +
173 +User wants to download only messages from specific senders or with certain subjects.
174 +
175 +```rust
176 +fn download_selective_messages(
177 + username: &str,
178 + password: &str,
179 + filter_from: Option<&str>,
180 + filter_subject: Option<&str>
181 +) -> Result<Vec<String>, Box<dyn std::error::Error>>
182 +{
183 + let mut client = Pop3Client::connect("mail.example.com", 110)?;
184 + client.authenticate(username, password)?;
185 +
186 + let messages = client.list()?;
187 + let mut downloaded = Vec::new();
188 +
189 + for (msg_num, _msg_size) in messages {
190 + // Retrieve message headers using TOP command (if supported)
191 + // Or retrieve full message and parse
192 + let message = client.retrieve(msg_num)?;
193 +
194 + let should_download = check_message_matches(&message, filter_from, filter_subject);
195 +
196 + if should_download {
197 + println!("Downloading message {}...", msg_num);
198 + downloaded.push(message);
199 + } else {
200 + println!("Skipping message {}...", msg_num);
201 + }
202 + }
203 +
204 + client.quit()?;
205 +
206 + Ok(downloaded)
207 +}
208 +
209 +fn check_message_matches(message: &str, filter_from: Option<&str>, filter_subject: Option<&str>) -> bool {
210 + let mut from_match = filter_from.is_none();
211 + let mut subject_match = filter_subject.is_none();
212 +
213 + for line in message.lines() {
214 + if let Some(filter) = filter_from {
215 + if line.starts_with("From:") && line.contains(filter) {
216 + from_match = true;
217 + }
218 + }
219 +
220 + if let Some(filter) = filter_subject {
221 + if line.starts_with("Subject:") && line.contains(filter) {
222 + subject_match = true;
223 + }
224 + }
225 +
226 + if line.is_empty() {
227 + break; // End of headers
228 + }
229 + }
230 +
231 + from_match && subject_match
232 +}
233 +
234 +fn main() {
235 + // Download only messages from boss@company.com
236 + match download_selective_messages(
237 + "user@example.com",
238 + "password",
239 + Some("boss@company.com"),
240 + None
241 + ) {
242 + Ok(messages) => {
243 + println!("Downloaded {} matching messages", messages.len());
244 + }
245 + Err(e) => eprintln!("Error: {}", e),
246 + }
247 +}
248 +```
249 +
250 +## Mobile Email Client
251 +
252 +### Scenario: Limited Bandwidth Email Access
253 +
254 +Mobile device with limited bandwidth wants to check emails efficiently.
255 +
256 +```rust
257 +fn check_emails_mobile(username: &str, password: &str) -> Result<(), Box<dyn std::error::Error>> {
258 + let mut client = Pop3Client::connect("mail.example.com", 110)?;
259 + client.authenticate(username, password)?;
260 +
261 + // Get mailbox statistics
262 + let (message_count, total_size) = client.stat()?;
263 + println!("Mailbox: {} messages, {} bytes total", message_count, total_size);
264 +
265 + // List messages to see sizes
266 + let messages = client.list()?;
267 +
268 + // Download only small messages (< 10KB) for preview
269 + for (msg_num, msg_size) in messages {
270 + if msg_size < 10240 {
271 + println!("\nDownloading small message {} ({} bytes)...", msg_num, msg_size);
272 + let message = client.retrieve(msg_num)?;
273 + display_message_preview(&message);
274 + } else {
275 + println!("\nMessage {} is large ({} bytes), skipping...", msg_num, msg_size);
276 + // Could use TOP command here to get headers only
277 + }
278 + }
279 +
280 + client.quit()?;
281 + Ok(())
282 +}
283 +
284 +fn display_message_preview(message: &str) {
285 + println!("--- Preview ---");
286 + let mut line_count = 0;
287 + for line in message.lines() {
288 + if line_count < 15 {
289 + println!("{}", line);
290 + line_count += 1;
291 + } else {
292 + println!("... (truncated)");
293 + break;
294 + }
295 + }
296 +}
297 +```
298 +
299 +## Email Backup Solution
300 +
301 +### Scenario: Automated Email Backup
302 +
303 +Automatically backup all emails from server to local storage periodically.
304 +
305 +```rust
306 +use std::path::Path;
307 +use chrono::Local;
308 +
309 +fn backup_emails(
310 + username: &str,
311 + password: &str,
312 + backup_dir: &str
313 +) -> Result<usize, Box<dyn std::error::Error>>
314 +{
315 + // Create dated backup directory
316 + let timestamp = Local::now().format("%Y%m%d_%H%M%S");
317 + let backup_path = format!("{}/{}", backup_dir, timestamp);
318 + std::fs::create_dir_all(&backup_path)?;
319 +
320 + let mut client = Pop3Client::connect("mail.example.com", 110)?;
321 + client.authenticate(username, password)?;
322 +
323 + let (message_count, _) = client.stat()?;
324 + println!("Backing up {} messages to {}...", message_count, backup_path);
325 +
326 + let mut backed_up = 0;
327 +
328 + for i in 1..=message_count {
329 + match client.retrieve(i) {
330 + Ok(message) => {
331 + // Generate unique filename
332 + let msg_id = extract_message_id(&message)
333 + .unwrap_or_else(|| format!("msg_{}", i));
334 + let filename = format!("{}/{}.eml", backup_path, sanitize_filename(&msg_id));
335 +
336 + std::fs::write(&filename, message)?;
337 + backed_up += 1;
338 +
339 + if backed_up % 10 == 0 {
340 + println!("Backed up {} messages...", backed_up);
341 + }
342 + }
343 + Err(e) => {
344 + eprintln!("Error backing up message {}: {}", i, e);
345 + }
346 + }
347 + }
348 +
349 + // Don't delete from server (backup only)
350 + client.quit()?;
351 +
352 + println!("Backup complete: {} messages saved to {}", backed_up, backup_path);
353 +
354 + Ok(backed_up)
355 +}
356 +
357 +fn extract_message_id(message: &str) -> Option<String> {
358 + for line in message.lines() {
359 + if line.starts_with("Message-ID:") || line.starts_with("Message-Id:") {
360 + return Some(line.split(':').nth(1)?.trim().to_string());
361 + }
362 + }
363 + None
364 +}
365 +
366 +fn sanitize_filename(name: &str) -> String {
367 + name.chars()
368 + .map(|c| if c.is_alphanumeric() || c == '-' || c == '_' { c } else { '_' })
369 + .collect()
370 +}
371 +
372 +fn main() {
373 + let backup_dir = "./email_backups";
374 + std::fs::create_dir_all(backup_dir).unwrap();
375 +
376 + match backup_emails("user@example.com", "password", backup_dir) {
377 + Ok(count) => println!("Successfully backed up {} emails", count),
378 + Err(e) => eprintln!("Backup failed: {}", e),
379 + }
380 +}
381 +```
382 +
383 +## Multi-Device Synchronization
384 +
385 +### Scenario: Leave Copy on Server
386 +
387 +User accesses email from multiple devices and wants to leave copies on server.
388 +
389 +```rust
390 +use std::collections::HashSet;
391 +use std::fs;
392 +
393 +fn sync_with_leave_copy(
394 + username: &str,
395 + password: &str,
396 + local_cache: &str
397 +) -> Result<(), Box<dyn std::error::Error>>
398 +{
399 + // Load list of already downloaded message IDs
400 + let downloaded_ids = load_downloaded_ids(local_cache)?;
401 +
402 + let mut client = Pop3Client::connect("mail.example.com", 110)?;
403 + client.authenticate(username, password)?;
404 +
405 + let messages = client.list()?;
406 + let mut new_messages = Vec::new();
407 +
408 + // Download only new messages
409 + for (msg_num, _) in messages {
410 + let message = client.retrieve(msg_num)?;
411 + let msg_id = extract_message_id(&message)
412 + .unwrap_or_else(|| format!("msg_{}", msg_num));
413 +
414 + if !downloaded_ids.contains(&msg_id) {
415 + println!("New message: {}", msg_id);
416 + new_messages.push((msg_id, message));
417 + }
418 + }
419 +
420 + // Don't delete from server
421 + client.quit()?;
422 +
423 + // Save new messages locally
424 + for (msg_id, message) in new_messages {
425 + let filename = format!("{}/{}.eml", local_cache, sanitize_filename(&msg_id));
426 + fs::write(&filename, message)?;
427 + save_downloaded_id(local_cache, &msg_id)?;
428 + }
429 +
430 + Ok(())
431 +}
432 +
433 +fn load_downloaded_ids(cache_dir: &str) -> Result<HashSet<String>, Box<dyn std::error::Error>> {
434 + let index_file = format!("{}/downloaded.txt", cache_dir);
435 +
436 + if !Path::new(&index_file).exists() {
437 + return Ok(HashSet::new());
438 + }
439 +
440 + let content = fs::read_to_string(index_file)?;
441 + Ok(content.lines().map(|s| s.to_string()).collect())
442 +}
443 +
444 +fn save_downloaded_id(cache_dir: &str, msg_id: &str) -> Result<(), Box<dyn std::error::Error>> {
445 + let index_file = format!("{}/downloaded.txt", cache_dir);
446 + let mut content = fs::read_to_string(&index_file).unwrap_or_default();
447 + content.push_str(&format!("{}\n", msg_id));
448 + fs::write(index_file, content)?;
449 + Ok(())
450 +}
451 +```
452 +
453 +## Email Migration
454 +
455 +### Scenario: Migrate from Old Server to New Server
456 +
457 +Transfer all emails from one server to another.
458 +
459 +```rust
460 +fn migrate_emails(
461 + old_server: &str,
462 + old_user: &str,
463 + old_pass: &str,
464 + new_server: &str,
465 + new_user: &str,
466 + new_pass: &str
467 +) -> Result<usize, Box<dyn std::error::Error>>
468 +{
469 + // Connect to old server (POP3)
470 + let mut old_client = Pop3Client::connect(old_server, 110)?;
471 + old_client.authenticate(old_user, old_pass)?;
472 +
473 + let (message_count, _) = old_client.stat()?;
474 + println!("Migrating {} messages from {} to {}...", message_count, old_server, new_server);
475 +
476 + // Connect to new server (SMTP for delivery)
477 + let mut new_client = SmtpClient::connect(new_server, 587)?;
478 + new_client.authenticate(new_user, new_pass)?;
479 +
480 + let mut migrated = 0;
481 +
482 + for i in 1..=message_count {
483 + // Retrieve from old server
484 + match old_client.retrieve(i) {
485 + Ok(message) => {
486 + // Send to new server
487 + match new_client.send_raw_message(&message) {
488 + Ok(_) => {
489 + migrated += 1;
490 + println!("Migrated message {}/{}", migrated, message_count);
491 +
492 + // Optionally delete from old server after successful migration
493 + // old_client.delete(i)?;
494 + }
495 + Err(e) => {
496 + eprintln!("Error migrating message {}: {}", i, e);
497 + }
498 + }
499 + }
500 + Err(e) => {
501 + eprintln!("Error retrieving message {}: {}", i, e);
502 + }
503 + }
504 +
505 + // Rate limiting - don't overwhelm servers
506 + std::thread::sleep(std::time::Duration::from_millis(100));
507 + }
508 +
509 + old_client.quit()?;
510 + new_client.quit()?;
511 +
512 + println!("Migration complete: {}/{} messages migrated", migrated, message_count);
513 +
514 + Ok(migrated)
515 +}
516 +```
517 +
518 +## Automated Email Processing
519 +
520 +### Scenario: Process Incoming Emails Automatically
521 +
522 +Automatically process emails based on rules (e.g., save attachments, forward specific emails).
523 +
524 +```rust
525 +use std::path::Path;
526 +
527 +struct EmailRule {
528 + from_filter: Option<String>,
529 + subject_filter: Option<String>,
530 + action: EmailAction,
531 +}
532 +
533 +enum EmailAction {
534 + SaveAttachments(String),
535 + Forward(String),
536 + Delete,
537 + Archive(String),
538 +}
539 +
540 +fn process_emails_automatically(
541 + username: &str,
542 + password: &str,
543 + rules: Vec<EmailRule>
544 +) -> Result<(), Box<dyn std::error::Error>>
545 +{
546 + let mut client = Pop3Client::connect("mail.example.com", 110)?;
547 + client.authenticate(username, password)?;
548 +
549 + let messages = client.list()?;
550 +
551 + for (msg_num, _) in messages {
552 + let message = client.retrieve(msg_num)?;
553 +
554 + // Apply rules
555 + for rule in &rules {
556 + if matches_rule(&message, rule) {
557 + println!("Message {} matches rule, applying action...", msg_num);
558 + apply_action(&message, &rule.action)?;
559 +
560 + if matches!(rule.action, EmailAction::Delete) {
561 + client.delete(msg_num)?;
562 + }
563 + }
564 + }
565 + }
566 +
567 + client.quit()?;
568 + Ok(())
569 +}
570 +
571 +fn matches_rule(message: &str, rule: &EmailRule) -> bool {
572 + let mut matches = true;
573 +
574 + if let Some(from_filter) = &rule.from_filter {
575 + let mut found = false;
576 + for line in message.lines() {
577 + if line.starts_with("From:") && line.contains(from_filter) {
578 + found = true;
579 + break;
580 + }
581 + }
582 + matches = matches && found;
583 + }
584 +
585 + if let Some(subject_filter) = &rule.subject_filter {
586 + let mut found = false;
587 + for line in message.lines() {
588 + if line.starts_with("Subject:") && line.contains(subject_filter) {
589 + found = true;
590 + break;
591 + }
592 + }
593 + matches = matches && found;
594 + }
595 +
596 + matches
597 +}
598 +
599 +fn apply_action(message: &str, action: &EmailAction) -> Result<(), Box<dyn std::error::Error>> {
600 + match action {
601 + EmailAction::SaveAttachments(dir) => {
602 + println!("Saving attachments to {}", dir);
603 + // Parse message and save attachments
604 + save_attachments(message, dir)?;
605 + }
606 + EmailAction::Forward(address) => {
607 + println!("Forwarding to {}", address);
608 + // Forward message
609 + forward_message(message, address)?;
610 + }
611 + EmailAction::Delete => {
612 + println!("Marking for deletion");
613 + }
614 + EmailAction::Archive(dir) => {
615 + println!("Archiving to {}", dir);
616 + let filename = format!("{}/archived_{}.eml", dir, chrono::Local::now().timestamp());
617 + std::fs::write(filename, message)?;
618 + }
619 + }
620 + Ok(())
621 +}
622 +
623 +fn save_attachments(message: &str, dir: &str) -> Result<(), Box<dyn std::error::Error>> {
624 + // Simplified - in production, use proper MIME parsing library
625 + std::fs::create_dir_all(dir)?;
626 + println!("Attachments saved (implementation simplified)");
627 + Ok(())
628 +}
629 +
630 +fn forward_message(message: &str, to: &str) -> Result<(), Box<dyn std::error::Error>> {
631 + println!("Forwarding message to {} (implementation simplified)", to);
632 + Ok(())
633 +}
634 +
635 +fn main() {
636 + let rules = vec![
637 + EmailRule {
638 + from_filter: Some("newsletter@company.com".to_string()),
639 + subject_filter: None,
640 + action: EmailAction::Archive("./newsletters".to_string()),
641 + },
642 + EmailRule {
643 + from_filter: Some("spam@".to_string()),
644 + subject_filter: None,
645 + action: EmailAction::Delete,
646 + },
647 + EmailRule {
648 + from_filter: None,
649 + subject_filter: Some("[URGENT]".to_string()),
650 + action: EmailAction::Forward("mobile@example.com".to_string()),
651 + },
652 + ];
653 +
654 + if let Err(e) = process_emails_automatically("user@example.com", "password", rules) {
655 + eprintln!("Error: {}", e);
656 + }
657 +}
658 +```
659 +
660 +## Error Recovery Scenarios
661 +
662 +### Scenario: Handling Network Interruptions
663 +
664 +Robust email retrieval with retry logic and error recovery.
665 +
666 +```rust
667 +use std::time::Duration;
668 +use std::thread;
669 +
670 +fn retrieve_with_retry(
671 + username: &str,
672 + password: &str,
673 + max_retries: u32
674 +) -> Result<Vec<String>, Box<dyn std::error::Error>>
675 +{
676 + let mut retry_count = 0;
677 +
678 + loop {
679 + match try_retrieve_emails(username, password) {
680 + Ok(messages) => return Ok(messages),
681 + Err(e) => {
682 + retry_count += 1;
683 +
684 + if retry_count >= max_retries {
685 + return Err(format!("Failed after {} retries: {}", max_retries, e).into());
686 + }
687 +
688 + let wait_time = 2u64.pow(retry_count); // Exponential backoff
689 + eprintln!("Error: {}. Retrying in {} seconds... (attempt {}/{})",
690 + e, wait_time, retry_count, max_retries);
691 + thread::sleep(Duration::from_secs(wait_time));
692 + }
693 + }
694 + }
695 +}
696 +
697 +fn try_retrieve_emails(username: &str, password: &str) -> Result<Vec<String>, Box<dyn std::error::Error>> {
698 + let mut client = Pop3Client::connect("mail.example.com", 110)?;
699 + client.authenticate(username, password)?;
700 +
701 + let messages = client.list()?;
702 + let mut retrieved = Vec::new();
703 +
704 + for (msg_num, _) in messages {
705 + match client.retrieve(msg_num) {
706 + Ok(msg) => retrieved.push(msg),
707 + Err(e) => {
708 + eprintln!("Warning: Failed to retrieve message {}: {}", msg_num, e);
709 + // Continue with other messages
710 + }
711 + }
712 + }
713 +
714 + client.quit()?;
715 + Ok(retrieved)
716 +}
717 +
718 +fn main() {
719 + match retrieve_with_retry("user@example.com", "password", 5) {
720 + Ok(messages) => {
721 + println!("Successfully retrieved {} messages", messages.len());
722 + }
723 + Err(e) => {
724 + eprintln!("Failed to retrieve emails: {}", e);
725 + }
726 + }
727 +}
728 +```
729 +
730 +### Scenario: Partial Download Recovery
731 +
732 +Resume downloading messages after interruption.
733 +
734 +```rust
735 +use std::collections::HashSet;
736 +
737 +fn resume_download(
738 + username: &str,
739 + password: &str,
740 + save_dir: &str
741 +) -> Result<(), Box<dyn std::error::Error>>
742 +{
743 + // Check which messages are already downloaded
744 + let downloaded = get_downloaded_messages(save_dir)?;
745 +
746 + let mut client = Pop3Client::connect("mail.example.com", 110)?;
747 + client.authenticate(username, password)?;
748 +
749 + let messages = client.list()?;
750 + let total = messages.len();
751 + let mut downloaded_count = downloaded.len();
752 +
753 + println!("Found {} total messages, {} already downloaded", total, downloaded_count);
754 +
755 + for (msg_num, _) in messages {
756 + if downloaded.contains(&msg_num) {
757 + println!("Skipping already downloaded message {}", msg_num);
758 + continue;
759 + }
760 +
761 + match client.retrieve(msg_num) {
762 + Ok(message) => {
763 + let filename = format!("{}/message_{}.eml", save_dir, msg_num);
764 + std::fs::write(&filename, message)?;
765 + downloaded_count += 1;
766 + println!("Downloaded message {}/{}", downloaded_count, total);
767 + }
768 + Err(e) => {
769 + eprintln!("Error downloading message {}: {}", msg_num, e);
770 + // Save progress and exit
771 + return Err(e);
772 + }
773 + }
774 + }
775 +
776 + client.quit()?;
777 + println!("Download complete!");
778 + Ok(())
779 +}
780 +
781 +fn get_downloaded_messages(save_dir: &str) -> Result<HashSet<usize>, Box<dyn std::error::Error>> {
782 + let mut downloaded = HashSet::new();
783 +
784 + if let Ok(entries) = std::fs::read_dir(save_dir) {
785 + for entry in entries.flatten() {
786 + if let Some(filename) = entry.file_name().to_str() {
787 + if filename.starts_with("message_") && filename.ends_with(".eml") {
788 + if let Some(num_str) = filename.strip_prefix("message_").and_then(|s| s.strip_suffix(".eml")) {
789 + if let Ok(num) = num_str.parse::<usize>() {
790 + downloaded.insert(num);
791 + }
792 + }
793 + }
794 + }
795 + }
796 + }
797 +
798 + Ok(downloaded)
799 +}
800 +```
801 +
802 +## Conclusion
803 +
804 +These scenarios demonstrate practical uses of POP3 in various contexts:
805 +
806 +1. **Basic Retrieval** - Simple email client functionality
807 +2. **Download & Delete** - Traditional POP3 usage
808 +3. **Selective Management** - Smart filtering and downloading
809 +4. **Mobile Access** - Bandwidth-conscious access
810 +5. **Backup** - Automated email backup
811 +6. **Multi-Device** - Leave copy on server
812 +7. **Migration** - Transfer emails between servers
813 +8. **Automation** - Rule-based processing
814 +9. **Error Recovery** - Robust error handling
815 +
816 +Each scenario includes working code examples that can be adapted for specific use cases. The key is to choose the right approach based on your requirements for message persistence, bandwidth usage, and device synchronization.
docs/TESTING.md new
+804
@@ -0,0 +1,804 @@
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