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(flavor = "multi_thread")]
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 tracing::info!(test_name = stringify!($name), "Test passed");
60 Ok(())
61 }
62 Err(e) => {
63 tracing::error!(
64 test_name = stringify!($name),
65 error = %e,
66 "Test failed"
67 );
68 tracing::debug!("Debug info: Check if required Docker images are available");
69 tracing::debug!("Debug info: Verify services are running correctly");
70 tracing::debug!("Debug info: Check container logs for more details");
71 Err(e)
72 }
73 }
74 }
75 };
76}
77
78pub struct ServiceSetup {
87 env: Arc<CleanroomEnvironment>,
88 services: Arc<RwLock<HashMap<String, ServiceHandle>>>,
89}
90
91impl ServiceSetup {
92 pub fn new(env: Arc<CleanroomEnvironment>) -> Self {
94 Self {
95 env,
96 services: Arc::new(RwLock::new(HashMap::new())),
97 }
98 }
99
100 pub async fn with_database(&self, image: &str) -> Result<()> {
102 self.with_service(
103 "database",
104 image,
105 Box::new(DatabaseServicePlugin::new(image)),
106 )
107 .await
108 }
109
110 pub async fn with_cache(&self, image: &str) -> Result<()> {
112 self.with_service("cache", image, Box::new(CacheServicePlugin::new(image)))
113 .await
114 }
115
116 pub async fn with_message_queue(&self, image: &str) -> Result<()> {
118 self.with_service(
119 "message_queue",
120 image,
121 Box::new(MessageQueueServicePlugin::new(image)),
122 )
123 .await
124 }
125
126 pub async fn with_web_server(&self, image: &str) -> Result<()> {
128 self.with_service(
129 "web_server",
130 image,
131 Box::new(WebServerServicePlugin::new(image)),
132 )
133 .await
134 }
135
136 async fn with_service(
138 &self,
139 service_type: &str,
140 image: &str,
141 plugin: Box<dyn ServicePlugin>,
142 ) -> Result<()> {
143 tracing::info!(
144 service_type = %service_type,
145 image = %image,
146 "Starting service"
147 );
148
149 self.env.register_service(plugin).await?;
151
152 let handle = self.env.start_service(service_type).await?;
154
155 let mut services = self.services.write().await;
157 services.insert(service_type.to_string(), handle);
158
159 tracing::info!(
160 service_type = %service_type,
161 "Service started successfully"
162 );
163 Ok(())
164 }
165
166 pub async fn get_database_url(&self) -> Result<String> {
168 let services = self.services.read().await;
169 if let Some(handle) = services.get("database") {
170 let default_port = "5432".to_string();
171 let port = handle.metadata.get("port").unwrap_or(&default_port);
172 Ok(format!(
173 "postgresql://postgres:password@localhost:{}/testdb",
174 port
175 ))
176 } else {
177 Err(CleanroomError::internal_error(
178 "Database service not started. Call with_database() first.",
179 ))
180 }
181 }
182
183 pub async fn get_cache_url(&self) -> Result<String> {
185 let services = self.services.read().await;
186 if let Some(handle) = services.get("cache") {
187 let default_port = "6379".to_string();
188 let port = handle.metadata.get("port").unwrap_or(&default_port);
189 Ok(format!("redis://localhost:{}", port))
190 } else {
191 Err(CleanroomError::internal_error(
192 "Cache service not started. Call with_cache() first.",
193 ))
194 }
195 }
196}
197
198pub struct TestContext {
200 env: Arc<CleanroomEnvironment>,
201 services: ServiceSetup,
202}
203
204impl TestContext {
205 pub fn new(env: CleanroomEnvironment) -> Self {
207 let env = Arc::new(env);
208 let services = ServiceSetup::new(env.clone());
209
210 Self { env, services }
211 }
212
213 pub fn services(&self) -> &ServiceSetup {
215 &self.services
216 }
217
218 pub fn env(&self) -> &Arc<CleanroomEnvironment> {
220 &self.env
221 }
222}
223
224#[derive(Debug)]
226pub struct DatabaseServicePlugin {
227 name: String,
228 image: String,
229}
230
231impl DatabaseServicePlugin {
232 pub fn new(image: &str) -> Self {
233 Self {
234 name: "database".to_string(),
235 image: image.to_string(),
236 }
237 }
238}
239
240impl ServicePlugin for DatabaseServicePlugin {
241 fn name(&self) -> &str {
242 &self.name
243 }
244
245 fn start(&self) -> Result<ServiceHandle> {
246 Ok(ServiceHandle {
249 id: format!("db_{}", uuid::Uuid::new_v4()),
250 service_name: self.name.clone(),
251 metadata: HashMap::from([
252 ("type".to_string(), "database".to_string()),
253 ("image".to_string(), self.image.clone()),
254 ("port".to_string(), "5432".to_string()),
255 ("status".to_string(), "running".to_string()),
256 ]),
257 })
258 }
259
260 fn stop(&self, _handle: ServiceHandle) -> Result<()> {
261 Ok(())
263 }
264
265 fn health_check(&self, _handle: &ServiceHandle) -> HealthStatus {
266 HealthStatus::Healthy
268 }
269}
270
271#[derive(Debug)]
273pub struct CacheServicePlugin {
274 name: String,
275 image: String,
276}
277
278impl CacheServicePlugin {
279 pub fn new(image: &str) -> Self {
280 Self {
281 name: "cache".to_string(),
282 image: image.to_string(),
283 }
284 }
285}
286
287impl ServicePlugin for CacheServicePlugin {
288 fn name(&self) -> &str {
289 &self.name
290 }
291
292 fn start(&self) -> Result<ServiceHandle> {
293 Ok(ServiceHandle {
294 id: format!("cache_{}", uuid::Uuid::new_v4()),
295 service_name: self.name.clone(),
296 metadata: HashMap::from([
297 ("type".to_string(), "cache".to_string()),
298 ("image".to_string(), self.image.clone()),
299 ("port".to_string(), "6379".to_string()),
300 ("status".to_string(), "running".to_string()),
301 ]),
302 })
303 }
304
305 fn stop(&self, _handle: ServiceHandle) -> Result<()> {
306 Ok(())
307 }
308
309 fn health_check(&self, _handle: &ServiceHandle) -> HealthStatus {
310 HealthStatus::Healthy
311 }
312}
313
314#[derive(Debug)]
316pub struct MessageQueueServicePlugin {
317 name: String,
318 image: String,
319}
320
321impl MessageQueueServicePlugin {
322 pub fn new(image: &str) -> Self {
323 Self {
324 name: "message_queue".to_string(),
325 image: image.to_string(),
326 }
327 }
328}
329
330impl ServicePlugin for MessageQueueServicePlugin {
331 fn name(&self) -> &str {
332 &self.name
333 }
334
335 fn start(&self) -> Result<ServiceHandle> {
336 Ok(ServiceHandle {
337 id: format!("mq_{}", uuid::Uuid::new_v4()),
338 service_name: self.name.clone(),
339 metadata: HashMap::from([
340 ("type".to_string(), "message_queue".to_string()),
341 ("image".to_string(), self.image.clone()),
342 ("port".to_string(), "5672".to_string()),
343 ("status".to_string(), "running".to_string()),
344 ]),
345 })
346 }
347
348 fn stop(&self, _handle: ServiceHandle) -> Result<()> {
349 Ok(())
350 }
351
352 fn health_check(&self, _handle: &ServiceHandle) -> HealthStatus {
353 HealthStatus::Healthy
354 }
355}
356
357#[derive(Debug)]
359pub struct WebServerServicePlugin {
360 name: String,
361 image: String,
362}
363
364impl WebServerServicePlugin {
365 pub fn new(image: &str) -> Self {
366 Self {
367 name: "web_server".to_string(),
368 image: image.to_string(),
369 }
370 }
371}
372
373impl ServicePlugin for WebServerServicePlugin {
374 fn name(&self) -> &str {
375 &self.name
376 }
377
378 fn start(&self) -> Result<ServiceHandle> {
379 Ok(ServiceHandle {
380 id: format!("web_{}", uuid::Uuid::new_v4()),
381 service_name: self.name.clone(),
382 metadata: HashMap::from([
383 ("type".to_string(), "web_server".to_string()),
384 ("image".to_string(), self.image.clone()),
385 ("port".to_string(), "8080".to_string()),
386 ("status".to_string(), "running".to_string()),
387 ]),
388 })
389 }
390
391 fn stop(&self, _handle: ServiceHandle) -> Result<()> {
392 Ok(())
393 }
394
395 fn health_check(&self, _handle: &ServiceHandle) -> HealthStatus {
396 HealthStatus::Healthy
397 }
398}
399
400pub async fn with_database(image: &str) -> Result<()> {
405 tracing::info!("Setting up database service with image: {}", image);
406
407 tracing::info!(
415 "Database service '{}' setup completed (placeholder implementation)",
416 image
417 );
418
419 Ok(())
422}
423
424pub async fn with_cache(image: &str) -> Result<()> {
426 tracing::info!("Setting up cache service with image: {}", image);
427
428 tracing::info!(
435 "Cache service '{}' setup completed (placeholder implementation)",
436 image
437 );
438 Ok(())
439}
440
441pub async fn with_message_queue(image: &str) -> Result<()> {
443 tracing::info!("Setting up message queue service with image: {}", image);
444
445 tracing::info!(
452 "Message queue service '{}' setup completed (placeholder implementation)",
453 image
454 );
455 Ok(())
456}
457
458pub async fn with_web_server(image: &str) -> Result<()> {
460 tracing::info!("Setting up web server service with image: {}", image);
461
462 tracing::info!(
469 "Web server service '{}' setup completed (placeholder implementation)",
470 image
471 );
472 Ok(())
473}