mongodb_integration/
mongodb_integration.rs

1//! MongoDB integration example demonstrating Docker-based testing with rust-test-harness
2//! 
3//! This shows real-world database testing patterns:
4//! 1. Testing with real Docker containers managed by hooks
5//! 2. Database operations (CRUD)
6//! 3. Error handling and edge cases
7//! 4. Setup and teardown with hooks
8
9use rust_test_harness::{test, run_tests_with_config, TestConfig, ContainerConfig, before_each};
10use std::time::Duration;
11
12// Mock MongoDB client for demonstration
13struct MongoClient {
14    container_id: String,
15    connected: bool,
16}
17
18impl MongoClient {
19    fn new(container_id: String) -> Self {
20        Self {
21            container_id,
22            connected: false,
23        }
24    }
25    
26    fn connect(&mut self) -> Result<(), String> {
27        // Simulate connection
28        std::thread::sleep(Duration::from_millis(20));
29        self.connected = true;
30        Ok(())
31    }
32    
33    fn disconnect(&mut self) -> Result<(), String> {
34        // Simulate disconnection
35        std::thread::sleep(Duration::from_millis(10));
36        self.connected = false;
37        Ok(())
38    }
39    
40    fn insert_document(&self, collection: &str, document: &str) -> Result<(), String> {
41        if !self.connected {
42            return Err("Not connected to MongoDB".to_string());
43        }
44        
45        // Simulate document insertion
46        std::thread::sleep(Duration::from_millis(5));
47        println!("๐Ÿ“„ Inserted document into collection '{}': {}", collection, document);
48        Ok(())
49    }
50    
51    fn find_documents(&self, collection: &str, query: &str) -> Result<Vec<String>, String> {
52        if !self.connected {
53            return Err("Not connected to MongoDB".to_string());
54        }
55        
56        // Simulate document retrieval
57        std::thread::sleep(Duration::from_millis(3));
58        println!("๐Ÿ” Found documents in collection '{}' with query: {}", collection, query);
59        
60        // Return mock documents
61        Ok(vec![
62            format!("Document 1 matching: {}", query),
63            format!("Document 2 matching: {}", query),
64        ])
65    }
66}
67
68fn main() {
69    println!("๐Ÿณ MongoDB Integration Example with Container Hooks");
70    println!("==================================================");
71    println!();
72    
73    // Define container configuration with auto-port for MongoDB
74    let mongo_container = ContainerConfig::new("mongo:5.0")
75        .auto_port(27017) // Automatically assign available host port for MongoDB
76        .env("MONGO_INITDB_ROOT_USERNAME", "admin")
77        .env("MONGO_INITDB_ROOT_PASSWORD", "password")
78        .name("test_mongodb")
79        .ready_timeout(Duration::from_secs(30));
80    
81    println!("๐Ÿ“‹ Container Configuration:");
82    println!("  Image: {}", mongo_container.image);
83    println!("  Auto-ports: {:?}", mongo_container.auto_ports);
84    println!("  Environment: {:?}", mongo_container.env);
85    println!("  Name: {:?}", mongo_container.name);
86    println!("  Ready Timeout: {:?}", mongo_container.ready_timeout);
87    println!("  Auto-cleanup: {}", mongo_container.auto_cleanup);
88    println!();
89    
90    // Clone for before_each hook
91    let mongo_container_before = mongo_container.clone();
92    
93    // Register before_each hook to start container
94    before_each(move |ctx| {
95        println!("๐Ÿ”„ before_each: Starting MongoDB container...");
96        
97        // Start the container and get ContainerInfo
98        let container_info = mongo_container_before.start()
99            .map_err(|e| format!("Failed to start container: {}", e))?;
100        
101        // Store container info in context for tests to access
102        ctx.set_data("mongo_container_info", container_info.clone());
103        
104        // Log container details including auto-assigned ports
105        println!("โœ… before_each: MongoDB container {} started", container_info.container_id);
106        println!("๐ŸŒ Container exposed on: {}", container_info.ports_summary());
107        if let Some(primary_url) = container_info.primary_url() {
108            println!("๐Ÿ”— Primary URL: {}", primary_url);
109        }
110        
111        Ok(())
112    });
113    
114    // Register after_each hook to stop container
115    let mongo_container_after = mongo_container.clone();
116    rust_test_harness::after_each(move |ctx| {
117        println!("๐Ÿ”„ after_each: Stopping MongoDB container...");
118        
119        // Get container info from context
120        let container_info = ctx.get_data::<rust_test_harness::ContainerInfo>("mongo_container_info")
121            .expect("Container info should be available");
122        
123        // Stop the container
124        mongo_container_after.stop(&container_info.container_id)
125            .map_err(|e| format!("Failed to stop container: {}", e))?;
126        
127        println!("โœ… after_each: MongoDB container {} stopped", container_info.container_id);
128        Ok(())
129    });
130    
131    // Test 1: Basic MongoDB operations
132    test("test_mongodb_basic_operations", |ctx| {
133        println!("๐Ÿงช Running test: test_mongodb_basic_operations");
134        
135        // Get container info from context
136        let container_info = ctx.get_data::<rust_test_harness::ContainerInfo>("mongo_container_info")
137            .expect("Container info should be available");
138        
139        // Show how to access port information
140        println!("๐ŸŒ Container {} is running on:", container_info.container_id);
141        println!("   Ports: {}", container_info.ports_summary());
142        if let Some(primary_url) = container_info.primary_url() {
143            println!("   Primary URL: {}", primary_url);
144        }
145        
146        // Get the MongoDB port (27017) and show the actual host port
147        if let Some(host_port) = container_info.host_port_for(27017) {
148            println!("   MongoDB accessible at: localhost:{}", host_port);
149        }
150        
151        // Create MongoDB client
152        let mut client = MongoClient::new(container_info.container_id.clone());
153        
154        // Connect to MongoDB
155        client.connect()?;
156        
157        // Insert a document
158        client.insert_document("users", r#"{"name": "John Doe", "email": "john@example.com"}"#)?;
159        
160        // Find documents
161        let documents = client.find_documents("users", r#"{"name": "John Doe"}"#)?;
162        assert_eq!(documents.len(), 2);
163        
164        // Disconnect
165        client.disconnect()?;
166        
167        println!("โœ… test_mongodb_basic_operations passed");
168        Ok(())
169    });
170    
171    // Test 2: Multiple operations
172    test("test_mongodb_multiple_operations", |ctx| {
173        println!("๐Ÿงช Running test: test_mongodb_multiple_operations");
174        
175        // Get container info from context
176        let container_info = ctx.get_data::<rust_test_harness::ContainerInfo>("mongo_container_info")
177            .expect("Container info should be available");
178        
179        // Show container status
180        println!("๐ŸŒ Container {} status:", container_info.container_id);
181        println!("   Active ports: {}", container_info.ports_summary());
182        
183        // Create MongoDB client
184        let mut client = MongoClient::new(container_info.container_id.clone());
185        
186        // Connect to MongoDB
187        client.connect()?;
188        
189        // Insert multiple documents
190        client.insert_document("products", r#"{"name": "Laptop", "price": 999.99}"#)?;
191        client.insert_document("products", r#"{"name": "Mouse", "price": 29.99}"#)?;
192        client.insert_document("products", r#"{"name": "Keyboard", "price": 79.99}"#)?;
193        
194        // Find documents
195        let documents = client.find_documents("products", r#"{"price": {"$gt": 50}}"#)?;
196        assert_eq!(documents.len(), 2); // Laptop and Keyboard
197        
198        // Disconnect
199        client.disconnect()?;
200        
201        println!("โœ… test_mongodb_multiple_operations passed");
202        Ok(())
203    });
204    
205    // Test 3: Error handling
206    test("test_mongodb_error_handling", |ctx| {
207        println!("๐Ÿงช Running test: test_mongodb_error_handling");
208        
209        // Get container ID from context
210        let container_id = ctx.get_data::<String>("mongo_container_id")
211            .expect("Container ID should be available")
212            .to_string();
213        
214        // Create MongoDB client
215        let client = MongoClient::new(container_id);
216        
217        // Try to insert without connecting (should fail)
218        let result = client.insert_document("users", r#"{"name": "Test"}"#);
219        assert!(result.is_err());
220        assert_eq!(result.unwrap_err(), "Not connected to MongoDB");
221        
222        println!("โœ… test_mongodb_error_handling passed");
223        Ok(())
224    });
225    
226    // Test 4: Performance testing
227    test("test_mongodb_performance", |ctx| {
228        println!("๐Ÿงช Running test: test_mongodb_performance");
229        
230        // Get container ID from context
231        let container_id = ctx.get_data::<String>("mongo_container_id")
232            .expect("Container ID should be available")
233            .to_string();
234        
235        // Create MongoDB client
236        let mut client = MongoClient::new(container_id);
237        
238        // Connect to MongoDB
239        client.connect()?;
240        
241        // Simulate bulk operations
242        for i in 0..100 {
243            client.insert_document("bulk_data", &format!(r#"{{"index": {}, "data": "bulk_item_{}"}}"#, i, i))?;
244        }
245        
246        // Simulate bulk retrieval
247        let documents = client.find_documents("bulk_data", r#"{"index": {"$lt": 50}}"#)?;
248        assert_eq!(documents.len(), 2); // Mock always returns 2
249        
250        // Disconnect
251        client.disconnect()?;
252        
253        println!("โœ… test_mongodb_performance passed");
254        Ok(())
255    });
256    
257    println!("\n๐Ÿš€ Running MongoDB integration tests...");
258    println!("   Each test will get a fresh MongoDB container via before_each");
259    println!("   Each test will clean up its container via after_each");
260    println!();
261    
262    // Run tests with container hooks enabled
263    let config = TestConfig {
264        skip_hooks: Some(false),
265        ..Default::default()
266    };
267    
268    let result = run_tests_with_config(config);
269    
270    println!("\n๐Ÿ“Š Test Results:");
271    if result == 0 {
272        println!("โœ… All MongoDB integration tests passed!");
273        println!("๐ŸŽฏ Container lifecycle management working correctly");
274        println!("๐Ÿณ Each test got its own MongoDB container");
275        println!("๐Ÿงน Each test cleaned up its container properly");
276    } else {
277        println!("โŒ Some tests failed");
278    }
279    
280    println!("\n๐Ÿ’ก Key Benefits of this approach:");
281    println!("   โ€ข Clean separation of concerns");
282    println!("   โ€ข Each test gets a fresh container");
283    println!("   โ€ข Automatic cleanup via after_each");
284    println!("   โ€ข Easy to configure containers");
285    println!("   โ€ข No complex global state management");
286}