container_port_access/
container_port_access.rs

1//! Container Port and URL Access Example
2//! 
3//! This example demonstrates how users can:
4//! 1. Get exact host ports for container ports
5//! 2. Get ready-to-use URLs for services
6//! 3. Access all port mappings and summaries
7//! 4. Use this information for testing and connections
8
9use rust_test_harness::{test, before_each, after_each, run_tests_with_config, TestConfig, ContainerConfig};
10use std::time::Duration;
11
12fn main() {
13    println!("๐ŸŒ Container Port and URL Access Example");
14    println!("========================================");
15    println!();
16    
17    // Example 1: Web service with multiple auto-ports
18    let web_container = ContainerConfig::new("nginx:alpine")
19        .auto_port(80)      // Auto-assign port for HTTP
20        .auto_port(443)     // Auto-assign port for HTTPS
21        .env("NGINX_HOST", "localhost")
22        .name("web_service")
23        .ready_timeout(Duration::from_secs(15));
24    
25    // Example 2: Database with auto-port
26    let db_container = ContainerConfig::new("postgres:13-alpine")
27        .auto_port(5432)    // Auto-assign port for PostgreSQL
28        .env("POSTGRES_PASSWORD", "testpass")
29        .env("POSTGRES_DB", "testdb")
30        .name("test_database")
31        .ready_timeout(Duration::from_secs(20));
32    
33    // Example 3: Mixed configuration (manual + auto ports)
34    let api_container = ContainerConfig::new("httpd:alpine")
35        .port(8080, 80)     // Manual port mapping
36        .auto_port(9090)    // Auto-assign port for API
37        .auto_port(9091)    // Auto-assign port for metrics
38        .env("API_VERSION", "v1")
39        .name("api_service")
40        .ready_timeout(Duration::from_secs(15));
41    
42    println!("๐Ÿ“‹ Container Configurations:");
43    println!("1. Web Service:");
44    println!("   - Image: {}", web_container.image);
45    println!("   - Auto-ports: {:?}", web_container.auto_ports);
46    println!("   - Will auto-assign host ports for container ports 80 and 443");
47    
48    println!("2. Database:");
49    println!("   - Image: {}", db_container.image);
50    println!("   - Auto-ports: {:?}", db_container.auto_ports);
51    println!("   - Will auto-assign host port for container port 5432");
52    
53    println!("3. API Service:");
54    println!("   - Image: {}", api_container.image);
55    println!("   - Manual ports: {:?}", api_container.ports);
56    println!("   - Auto-ports: {:?}", api_container.auto_ports);
57    println!("   - Manual mapping: 8080 -> 80, Auto-assign: 9090, 9091");
58    println!();
59    
60    // Clone containers for hooks
61    let web_before = web_container.clone();
62    let db_before = db_container.clone();
63    let api_before = api_container.clone();
64    
65    // Start containers in before_each
66    before_each(move |ctx| {
67        println!("๐Ÿš€ before_each: Starting containers and capturing port info...");
68        
69        // Start web container
70        let web_info = web_before.start()
71            .map_err(|e| format!("Failed to start web container: {}", e))?;
72        ctx.set_data("web_container_info", web_info.clone());
73        
74        println!("โœ… Web container started: {}", web_info.container_id);
75        println!("   ๐Ÿ“ Port mappings: {}", web_info.ports_summary());
76        println!("   ๐Ÿ”— Primary URL: {}", web_info.primary_url().unwrap_or("None"));
77        
78        // Show specific port access
79        if let Some(http_port) = web_info.host_port_for(80) {
80            println!("   ๐ŸŒ HTTP accessible at: http://localhost:{}", http_port);
81        }
82        if let Some(https_port) = web_info.host_port_for(443) {
83            println!("   ๐Ÿ”’ HTTPS accessible at: https://localhost:{}", https_port);
84        }
85        
86        // Start database container
87        let db_info = db_before.start()
88            .map_err(|e| format!("Failed to start db container: {}", e))?;
89        ctx.set_data("db_container_info", db_info.clone());
90        
91        println!("โœ… Database container started: {}", db_info.container_id);
92        println!("   ๐Ÿ“ Port mappings: {}", db_info.ports_summary());
93        if let Some(db_port) = db_info.host_port_for(5432) {
94            println!("   ๐Ÿ—„๏ธ PostgreSQL accessible at: localhost:{}", db_port);
95            println!("   ๐Ÿ“ Connection string: postgresql://user:pass@localhost:{}/testdb", db_port);
96        }
97        
98        // Start API container
99        let api_info = api_before.start()
100            .map_err(|e| format!("Failed to start api container: {}", e))?;
101        ctx.set_data("api_container_info", api_info.clone());
102        
103        println!("โœ… API container started: {}", api_info.container_id);
104        println!("   ๐Ÿ“ Port mappings: {}", api_info.ports_summary());
105        
106        // Show both manual and auto ports
107        if let Some(http_port) = api_info.host_port_for(80) {
108            println!("   ๐ŸŒ HTTP API at: http://localhost:{} (manual mapping)", http_port);
109        }
110        if let Some(api_port) = api_info.host_port_for(9090) {
111            println!("   ๐Ÿ”ง API endpoint at: http://localhost:{} (auto-assigned)", api_port);
112        }
113        if let Some(metrics_port) = api_info.host_port_for(9091) {
114            println!("   ๐Ÿ“Š Metrics at: http://localhost:{} (auto-assigned)", metrics_port);
115        }
116        
117        println!("๐ŸŽ‰ All containers started with auto-assigned ports!");
118        Ok(())
119    });
120    
121    // Cleanup containers in after_each
122    let web_after = web_container.clone();
123    let db_after = db_container.clone();
124    let api_after = api_container.clone();
125    
126    after_each(move |ctx| {
127        println!("๐Ÿงน after_each: Cleaning up containers...");
128        
129        if let Some(web_info) = ctx.get_data::<rust_test_harness::ContainerInfo>("web_container_info") {
130            let _ = web_after.stop(&web_info.container_id);
131            println!("๐Ÿ›‘ Stopped web container: {}", web_info.container_id);
132        }
133        
134        if let Some(db_info) = ctx.get_data::<rust_test_harness::ContainerInfo>("db_container_info") {
135            let _ = db_after.stop(&db_info.container_id);
136            println!("๐Ÿ›‘ Stopped database container: {}", db_info.container_id);
137        }
138        
139        if let Some(api_info) = ctx.get_data::<rust_test_harness::ContainerInfo>("api_container_info") {
140            let _ = api_after.stop(&api_info.container_id);
141            println!("๐Ÿ›‘ Stopped API container: {}", api_info.container_id);
142        }
143        
144        println!("โœ… All containers cleaned up!");
145        Ok(())
146    });
147    
148    // Test 1: Web service port access
149    test("test_web_service_port_access", |ctx| {
150        println!("๐Ÿงช Testing web service port access...");
151        
152        let web_info = ctx.get_data::<rust_test_harness::ContainerInfo>("web_container_info")
153            .expect("Web container info should be available");
154        
155        println!("๐Ÿ” Demonstrating port access methods:");
156        
157        // Method 1: Get specific host port for container port
158        if let Some(http_port) = web_info.host_port_for(80) {
159            println!("   โœ… HTTP port 80 is mapped to host port: {}", http_port);
160            println!("      ๐Ÿ’ก Use this for HTTP client connections");
161        }
162        
163        if let Some(https_port) = web_info.host_port_for(443) {
164            println!("   โœ… HTTPS port 443 is mapped to host port: {}", https_port);
165            println!("      ๐Ÿ’ก Use this for HTTPS client connections");
166        }
167        
168        // Method 2: Get ready-to-use URLs
169        if let Some(http_url) = web_info.url_for_port(80) {
170            println!("   ๐Ÿ”— HTTP URL: {}", http_url);
171            println!("      ๐Ÿ’ก Ready to use in HTTP requests");
172        }
173        
174        if let Some(https_url) = web_info.url_for_port(443) {
175            println!("   ๐Ÿ”— HTTPS URL: {}", https_url);
176            println!("      ๐Ÿ’ก Ready to use in HTTPS requests (change http:// to https://)");
177        }
178        
179        // Method 3: Get all URLs
180        println!("   ๐Ÿ“‹ All service URLs:");
181        for (i, url) in web_info.urls.iter().enumerate() {
182            println!("      {}. {}", i + 1, url);
183        }
184        
185        // Method 4: Get all port mappings
186        println!("   ๐Ÿ“Š All port mappings:");
187        for (host_port, container_port) in &web_info.port_mappings {
188            println!("      Host {} -> Container {}", host_port, container_port);
189        }
190        
191        // Verify ports are different (auto-assigned)
192        assert!(web_info.host_port_for(80).unwrap() != web_info.host_port_for(443).unwrap(), 
193                "Auto-assigned ports should be different");
194        
195        println!("โœ… Web service port access test passed");
196        Ok(())
197    });
198    
199    // Test 2: Database connection string generation
200    test("test_database_connection_info", |ctx| {
201        println!("๐Ÿงช Testing database connection info generation...");
202        
203        let db_info = ctx.get_data::<rust_test_harness::ContainerInfo>("db_container_info")
204            .expect("Database container info should be available");
205        
206        println!("๐Ÿ—„๏ธ Database connection information:");
207        
208        // Get the auto-assigned port for PostgreSQL
209        let db_port = db_info.host_port_for(5432)
210            .expect("PostgreSQL port should be exposed");
211        
212        println!("   ๐Ÿ“ PostgreSQL running on: localhost:{}", db_port);
213        
214        // Generate connection strings using the auto-assigned port
215        let connection_string = format!("postgresql://admin:testpass@localhost:{}/testdb", db_port);
216        let jdbc_url = format!("jdbc:postgresql://localhost:{}/testdb", db_port);
217        let docker_internal = format!("postgresql://admin:testpass@{}:5432/testdb", db_info.container_id);
218        
219        println!("   ๐Ÿ”— Connection strings:");
220        println!("      Standard: {}", connection_string);
221        println!("      JDBC: {}", jdbc_url);
222        println!("      Docker internal: {}", docker_internal);
223        
224        // Show how to use this in real applications
225        println!("   ๐Ÿ’ก Usage examples:");
226        println!("      - Set DATABASE_URL={}", connection_string);
227        println!("      - Use in tests: connect to localhost:{}", db_port);
228        println!("      - Container-to-container: use port 5432");
229        
230        // Verify port is valid
231        assert!(db_port > 0, "Database port should be valid");
232        println!("โœ… Database connection info test passed");
233        
234        Ok(())
235    });
236    
237    // Test 3: Mixed port configuration
238    test("test_mixed_port_configuration", |ctx| {
239        println!("๐Ÿงช Testing mixed port configuration (manual + auto)...");
240        
241        let api_info = ctx.get_data::<rust_test_harness::ContainerInfo>("api_container_info")
242            .expect("API container info should be available");
243        
244        println!("๐Ÿ”€ Mixed port configuration analysis:");
245        
246        // Manual port mapping
247        let http_port = api_info.host_port_for(80)
248            .expect("HTTP port should be mapped");
249        println!("   ๐Ÿ“Œ Manual mapping - HTTP port 80 -> host port: {}", http_port);
250        assert_eq!(http_port, 8080, "Manual port should be exactly as specified");
251        
252        // Auto-assigned ports
253        let api_port = api_info.host_port_for(9090)
254            .expect("API port should be auto-assigned");
255        let metrics_port = api_info.host_port_for(9091)
256            .expect("Metrics port should be auto-assigned");
257        
258        println!("   ๐ŸŽฒ Auto-assigned - API port 9090 -> host port: {}", api_port);
259        println!("   ๐ŸŽฒ Auto-assigned - Metrics port 9091 -> host port: {}", metrics_port);
260        
261        // Verify auto-assigned ports are different from manual port
262        assert_ne!(api_port, 8080, "Auto-assigned port should be different from manual port");
263        assert_ne!(metrics_port, 8080, "Auto-assigned port should be different from manual port");
264        assert_ne!(api_port, metrics_port, "Auto-assigned ports should be different from each other");
265        
266        println!("   ๐ŸŒ Service endpoints:");
267        println!("      Main API: http://localhost:{}/", http_port);
268        println!("      API v2: http://localhost:{}/", api_port);
269        println!("      Metrics: http://localhost:{}/metrics", metrics_port);
270        
271        // Show complete port summary
272        println!("   ๐Ÿ“‹ Complete port summary: {}", api_info.ports_summary());
273        
274        println!("โœ… Mixed port configuration test passed");
275        Ok(())
276    });
277    
278    // Test 4: Container info convenience methods
279    test("test_container_info_convenience_methods", |ctx| {
280        println!("๐Ÿงช Testing ContainerInfo convenience methods...");
281        
282        let web_info = ctx.get_data::<rust_test_harness::ContainerInfo>("web_container_info")
283            .expect("Web container info should be available");
284        
285        println!("๐Ÿ”ง Testing all ContainerInfo methods:");
286        
287        // Test primary_url()
288        if let Some(primary_url) = web_info.primary_url() {
289            println!("   โœ… primary_url(): {}", primary_url);
290            assert!(primary_url.starts_with("http://localhost:"), "Primary URL should be valid");
291        }
292        
293        // Test url_for_port()
294        if let Some(url_80) = web_info.url_for_port(80) {
295            println!("   โœ… url_for_port(80): {}", url_80);
296            assert!(url_80.contains("localhost:"), "URL should contain localhost");
297        }
298        
299        // Test host_port_for()
300        if let Some(port_80) = web_info.host_port_for(80) {
301            println!("   โœ… host_port_for(80): {}", port_80);
302            assert!(port_80 > 0, "Port should be positive");
303        }
304        
305        // Test ports_summary()
306        let summary = web_info.ports_summary();
307        println!("   โœ… ports_summary(): {}", summary);
308        assert!(summary.contains("->"), "Summary should contain port mappings");
309        
310        // Test direct field access
311        println!("   โœ… container_id: {}", web_info.container_id);
312        println!("   โœ… image: {}", web_info.image);
313        println!("   โœ… auto_cleanup: {}", web_info.auto_cleanup);
314        
315        println!("   ๐Ÿ“Š All URLs:");
316        for (i, url) in web_info.urls.iter().enumerate() {
317            println!("      {}. {}", i + 1, url);
318        }
319        
320        println!("   ๐Ÿ“Š All port mappings:");
321        for (host_port, container_port) in &web_info.port_mappings {
322            println!("      {} -> {}", host_port, container_port);
323        }
324        
325        println!("โœ… Container info convenience methods test passed");
326        Ok(())
327    });
328    
329    // Test 5: Real-world usage patterns
330    test("test_real_world_usage_patterns", |ctx| {
331        println!("๐Ÿงช Testing real-world usage patterns...");
332        
333        let web_info = ctx.get_data::<rust_test_harness::ContainerInfo>("web_container_info")
334            .expect("Web container info should be available");
335        let db_info = ctx.get_data::<rust_test_harness::ContainerInfo>("db_container_info")
336            .expect("Database container info should be available");
337        
338        println!("๐ŸŒ Real-world usage examples:");
339        
340        // Pattern 1: HTTP client testing
341        if let Some(web_url) = web_info.primary_url() {
342            println!("   ๐ŸŒ HTTP Client Testing:");
343            println!("      Base URL: {}", web_url);
344            println!("      GET {}/health", web_url);
345            println!("      POST {}/api/users", web_url);
346            println!("      Code: let response = reqwest::get(\"{}/health\").await?;", web_url);
347        }
348        
349        // Pattern 2: Database testing
350        if let Some(db_port) = db_info.host_port_for(5432) {
351            println!("   ๐Ÿ—„๏ธ Database Testing:");
352            println!("      Connection: localhost:{}", db_port);
353            println!("      Code: let conn = PgConnection::establish(\"postgresql://user:pass@localhost:{}/db\")?;", db_port);
354        }
355        
356        // Pattern 3: Environment variable setup
357        println!("   ๐Ÿ”ง Environment Variables:");
358        if let Some(web_port) = web_info.host_port_for(80) {
359            println!("      export WEB_SERVICE_URL=http://localhost:{}", web_port);
360        }
361        if let Some(db_port) = db_info.host_port_for(5432) {
362            println!("      export DATABASE_URL=postgresql://localhost:{}/testdb", db_port);
363        }
364        
365        // Pattern 4: Docker-compose style networking
366        println!("   ๐Ÿณ Container Networking:");
367        println!("      External: Use auto-assigned host ports");
368        println!("      Internal: Use original container ports");
369        println!("      Web -> DB: {}:5432 (container-to-container)", db_info.container_id);
370        
371        // Pattern 5: Health checks
372        println!("   โค๏ธ Health Checks:");
373        if let Some(web_url) = web_info.primary_url() {
374            println!("      Health endpoint: {}/health", web_url);
375            println!("      Ready endpoint: {}/ready", web_url);
376        }
377        
378        println!("โœ… Real-world usage patterns test passed");
379        Ok(())
380    });
381    
382    println!("๐Ÿš€ Running container port access tests...");
383    println!();
384    
385    // Run the tests
386    let config = TestConfig {
387        html_report: Some("container-port-access-report.html".to_string()),
388        ..Default::default()
389    };
390    
391    let exit_code = run_tests_with_config(config);
392    
393    println!();
394    if exit_code == 0 {
395        println!("๐ŸŽ‰ All tests passed! Port access functionality is working correctly.");
396        println!("๐Ÿ“Š Check the HTML report for detailed results.");
397        println!();
398        println!("๐Ÿ’ก Key Takeaways:");
399        println!("   โ€ข Use auto_port() to avoid port conflicts");
400        println!("   โ€ข Access host ports with host_port_for(container_port)");
401        println!("   โ€ข Get ready-to-use URLs with url_for_port(container_port)");
402        println!("   โ€ข Use ports_summary() for human-readable port info");
403        println!("   โ€ข ContainerInfo provides complete access to all port information");
404    } else {
405        println!("โŒ Some tests failed. Check the output above for details.");
406    }
407    
408    std::process::exit(exit_code);
409}