1use crate::cleanroom::{CleanroomEnvironment, HealthStatus, ServiceHandle, ServicePlugin};
8use crate::error::{CleanroomError, Result};
9use std::collections::HashMap;
10use std::sync::Arc;
11use tokio::sync::RwLock;
12
13#[macro_export]
36macro_rules! cleanroom_test {
37 ($(#[$meta:meta])* $vis:vis async fn $name:ident() $body:block) => {
38 $(#[$meta])*
39 #[tokio::test]
40 $vis async fn $name() -> Result<(), $crate::error::CleanroomError> {
41 let env = $crate::cleanroom::CleanroomEnvironment::new().await
43 .map_err(|e| $crate::error::CleanroomError::internal_error("Failed to create cleanroom environment")
44 .with_context("Cleanroom environment initialization failed")
45 .with_source(e.to_string())
46 )?;
47
48 let mut test_context = $crate::macros::TestContext::new(env);
50
51 let result = async {
53 $body
54 }.await;
55
56 match result {
58 Ok(_) => {
59 println!("✅ Test '{}' passed", stringify!($name));
60 Ok(())
61 }
62 Err(e) => {
63 eprintln!("❌ Test '{}' failed: {}", stringify!($name), e);
64 eprintln!("💡 Debug info:");
65 eprintln!(" - Check if required Docker images are available");
66 eprintln!(" - Verify services are running correctly");
67 eprintln!(" - Check container logs for more details");
68 Err(e)
69 }
70 }
71 }
72 };
73}
74
75pub struct ServiceSetup {
84 env: Arc<CleanroomEnvironment>,
85 services: Arc<RwLock<HashMap<String, ServiceHandle>>>,
86}
87
88impl ServiceSetup {
89 pub fn new(env: Arc<CleanroomEnvironment>) -> Self {
91 Self {
92 env,
93 services: Arc::new(RwLock::new(HashMap::new())),
94 }
95 }
96
97 pub async fn with_database(&self, image: &str) -> Result<()> {
99 self.with_service(
100 "database",
101 image,
102 Box::new(DatabaseServicePlugin::new(image)),
103 )
104 .await
105 }
106
107 pub async fn with_cache(&self, image: &str) -> Result<()> {
109 self.with_service("cache", image, Box::new(CacheServicePlugin::new(image)))
110 .await
111 }
112
113 pub async fn with_message_queue(&self, image: &str) -> Result<()> {
115 self.with_service(
116 "message_queue",
117 image,
118 Box::new(MessageQueueServicePlugin::new(image)),
119 )
120 .await
121 }
122
123 pub async fn with_web_server(&self, image: &str) -> Result<()> {
125 self.with_service(
126 "web_server",
127 image,
128 Box::new(WebServerServicePlugin::new(image)),
129 )
130 .await
131 }
132
133 async fn with_service(
135 &self,
136 service_type: &str,
137 image: &str,
138 plugin: Box<dyn ServicePlugin>,
139 ) -> Result<()> {
140 println!("🚀 Starting {} service with image: {}", service_type, image);
141
142 self.env.register_service(plugin).await?;
144
145 let handle = self.env.start_service(service_type).await?;
147
148 let mut services = self.services.write().await;
150 services.insert(service_type.to_string(), handle);
151
152 println!("✅ {} service started successfully", service_type);
153 Ok(())
154 }
155
156 pub async fn get_database_url(&self) -> Result<String> {
158 let services = self.services.read().await;
159 if let Some(handle) = services.get("database") {
160 let default_port = "5432".to_string();
161 let port = handle.metadata.get("port").unwrap_or(&default_port);
162 Ok(format!(
163 "postgresql://postgres:password@localhost:{}/testdb",
164 port
165 ))
166 } else {
167 Err(CleanroomError::internal_error(
168 "Database service not started. Call with_database() first.",
169 ))
170 }
171 }
172
173 pub async fn get_cache_url(&self) -> Result<String> {
175 let services = self.services.read().await;
176 if let Some(handle) = services.get("cache") {
177 let default_port = "6379".to_string();
178 let port = handle.metadata.get("port").unwrap_or(&default_port);
179 Ok(format!("redis://localhost:{}", port))
180 } else {
181 Err(CleanroomError::internal_error(
182 "Cache service not started. Call with_cache() first.",
183 ))
184 }
185 }
186}
187
188pub struct TestContext {
190 env: Arc<CleanroomEnvironment>,
191 services: ServiceSetup,
192}
193
194impl TestContext {
195 pub fn new(env: CleanroomEnvironment) -> Self {
197 let env = Arc::new(env);
198 let services = ServiceSetup::new(env.clone());
199
200 Self { env, services }
201 }
202
203 pub fn services(&self) -> &ServiceSetup {
205 &self.services
206 }
207
208 pub fn env(&self) -> &Arc<CleanroomEnvironment> {
210 &self.env
211 }
212}
213
214#[derive(Debug)]
216pub struct DatabaseServicePlugin {
217 name: String,
218 image: String,
219}
220
221impl DatabaseServicePlugin {
222 pub fn new(image: &str) -> Self {
223 Self {
224 name: "database".to_string(),
225 image: image.to_string(),
226 }
227 }
228}
229
230impl ServicePlugin for DatabaseServicePlugin {
231 fn name(&self) -> &str {
232 &self.name
233 }
234
235 fn start(&self) -> Result<ServiceHandle> {
236 Ok(ServiceHandle {
239 id: format!("db_{}", uuid::Uuid::new_v4()),
240 service_name: self.name.clone(),
241 metadata: HashMap::from([
242 ("type".to_string(), "database".to_string()),
243 ("image".to_string(), self.image.clone()),
244 ("port".to_string(), "5432".to_string()),
245 ("status".to_string(), "running".to_string()),
246 ]),
247 })
248 }
249
250 fn stop(&self, _handle: ServiceHandle) -> Result<()> {
251 Ok(())
253 }
254
255 fn health_check(&self, _handle: &ServiceHandle) -> HealthStatus {
256 HealthStatus::Healthy
258 }
259}
260
261#[derive(Debug)]
263pub struct CacheServicePlugin {
264 name: String,
265 image: String,
266}
267
268impl CacheServicePlugin {
269 pub fn new(image: &str) -> Self {
270 Self {
271 name: "cache".to_string(),
272 image: image.to_string(),
273 }
274 }
275}
276
277impl ServicePlugin for CacheServicePlugin {
278 fn name(&self) -> &str {
279 &self.name
280 }
281
282 fn start(&self) -> Result<ServiceHandle> {
283 Ok(ServiceHandle {
284 id: format!("cache_{}", uuid::Uuid::new_v4()),
285 service_name: self.name.clone(),
286 metadata: HashMap::from([
287 ("type".to_string(), "cache".to_string()),
288 ("image".to_string(), self.image.clone()),
289 ("port".to_string(), "6379".to_string()),
290 ("status".to_string(), "running".to_string()),
291 ]),
292 })
293 }
294
295 fn stop(&self, _handle: ServiceHandle) -> Result<()> {
296 Ok(())
297 }
298
299 fn health_check(&self, _handle: &ServiceHandle) -> HealthStatus {
300 HealthStatus::Healthy
301 }
302}
303
304#[derive(Debug)]
306pub struct MessageQueueServicePlugin {
307 name: String,
308 image: String,
309}
310
311impl MessageQueueServicePlugin {
312 pub fn new(image: &str) -> Self {
313 Self {
314 name: "message_queue".to_string(),
315 image: image.to_string(),
316 }
317 }
318}
319
320impl ServicePlugin for MessageQueueServicePlugin {
321 fn name(&self) -> &str {
322 &self.name
323 }
324
325 fn start(&self) -> Result<ServiceHandle> {
326 Ok(ServiceHandle {
327 id: format!("mq_{}", uuid::Uuid::new_v4()),
328 service_name: self.name.clone(),
329 metadata: HashMap::from([
330 ("type".to_string(), "message_queue".to_string()),
331 ("image".to_string(), self.image.clone()),
332 ("port".to_string(), "5672".to_string()),
333 ("status".to_string(), "running".to_string()),
334 ]),
335 })
336 }
337
338 fn stop(&self, _handle: ServiceHandle) -> Result<()> {
339 Ok(())
340 }
341
342 fn health_check(&self, _handle: &ServiceHandle) -> HealthStatus {
343 HealthStatus::Healthy
344 }
345}
346
347#[derive(Debug)]
349pub struct WebServerServicePlugin {
350 name: String,
351 image: String,
352}
353
354impl WebServerServicePlugin {
355 pub fn new(image: &str) -> Self {
356 Self {
357 name: "web_server".to_string(),
358 image: image.to_string(),
359 }
360 }
361}
362
363impl ServicePlugin for WebServerServicePlugin {
364 fn name(&self) -> &str {
365 &self.name
366 }
367
368 fn start(&self) -> Result<ServiceHandle> {
369 Ok(ServiceHandle {
370 id: format!("web_{}", uuid::Uuid::new_v4()),
371 service_name: self.name.clone(),
372 metadata: HashMap::from([
373 ("type".to_string(), "web_server".to_string()),
374 ("image".to_string(), self.image.clone()),
375 ("port".to_string(), "8080".to_string()),
376 ("status".to_string(), "running".to_string()),
377 ]),
378 })
379 }
380
381 fn stop(&self, _handle: ServiceHandle) -> Result<()> {
382 Ok(())
383 }
384
385 fn health_check(&self, _handle: &ServiceHandle) -> HealthStatus {
386 HealthStatus::Healthy
387 }
388}
389
390pub async fn with_database(image: &str) -> Result<()> {
395 println!("🚀 Setting up database with image: {}", image);
398 println!("✅ Database service configured");
399 Ok(())
400}
401
402pub async fn with_cache(image: &str) -> Result<()> {
404 println!("🚀 Setting up cache with image: {}", image);
405 println!("✅ Cache service configured");
406 Ok(())
407}
408
409pub async fn with_message_queue(image: &str) -> Result<()> {
411 println!("🚀 Setting up message queue with image: {}", image);
412 println!("✅ Message queue service configured");
413 Ok(())
414}
415
416pub async fn with_web_server(image: &str) -> Result<()> {
418 println!("🚀 Setting up web server with image: {}", image);
419 println!("✅ Web server service configured");
420 Ok(())
421}
422
423#[cfg(test)]
424mod tests {
425 use super::*;
426
427 #[tokio::test]
428 async fn test_service_setup_creation() -> Result<()> {
429 let env = CleanroomEnvironment::new().await?;
430 let setup = ServiceSetup::new(Arc::new(env));
431
432 assert!(setup.services.read().await.is_empty());
434 Ok(())
435 }
436
437 #[tokio::test]
438 async fn test_jane_friendly_functions() {
439 assert!(with_database("postgres:15").await.is_ok());
441 assert!(with_cache("redis:7").await.is_ok());
442 assert!(with_message_queue("rabbitmq").await.is_ok());
443 assert!(with_web_server("nginx").await.is_ok());
444 }
445}