1use crate::cleanroom::{CleanroomEnvironment, ServicePlugin, ServiceHandle, HealthStatus};
7use crate::backend::{Backend, TestcontainerBackend, Cmd};
8use crate::cli::{validate_config, init_project, list_plugins};
9use crate::error::{CleanroomError, Result};
10use crate::policy::{Policy, SecurityLevel};
11use std::future::Future;
12use std::pin::Pin;
13use crate::scenario::scenario;
14use std::collections::HashMap;
15use tempfile::TempDir;
16
17#[derive(Debug, Clone, serde::Serialize)]
19pub struct FrameworkTestResults {
20 pub total_tests: u32,
22 pub passed_tests: u32,
24 pub failed_tests: u32,
26 pub total_duration_ms: u64,
28 pub test_results: Vec<TestResult>,
30}
31
32#[derive(Debug, Clone, serde::Serialize)]
34pub struct TestResult {
35 pub name: String,
37 pub passed: bool,
39 pub duration_ms: u64,
41 pub error: Option<String>,
43}
44
45pub async fn run_framework_tests() -> Result<FrameworkTestResults> {
47 let start_time = std::time::Instant::now();
48 let mut results = FrameworkTestResults {
49 total_tests: 0,
50 passed_tests: 0,
51 failed_tests: 0,
52 total_duration_ms: 0,
53 test_results: Vec::new(),
54 };
55
56 let environment = CleanroomEnvironment::new().await
58 .map_err(|e| CleanroomError::internal_error("Failed to create cleanroom environment")
59 .with_context("Framework self-testing initialization failed")
60 .with_source(e.to_string()))?;
61
62 let test_names = vec![
64 "validate_framework",
65 "test_container_lifecycle",
66 "test_plugin_system",
67 "test_cli_functionality",
68 "test_otel_integration",
69 ];
70
71 for test_name in test_names {
72 results.total_tests += 1;
73 let test_start = std::time::Instant::now();
74
75 let test_result = match test_name {
76 "validate_framework" => {
77 environment.execute_test(test_name, || {
79 Ok(())
81 }).await?;
82 validate_framework().await
83 },
84 "test_container_lifecycle" => {
85 environment.execute_test(test_name, || Ok(())).await?;
86 test_container_lifecycle().await
87 },
88 "test_plugin_system" => {
89 environment.execute_test(test_name, || Ok(())).await?;
90 test_plugin_system().await
91 },
92 "test_cli_functionality" => {
93 environment.execute_test(test_name, || Ok(())).await?;
94 test_cli_functionality().await
95 },
96 "test_otel_integration" => {
97 environment.execute_test(test_name, || Ok(())).await?;
98 test_otel_integration().await
99 },
100 _ => {
101 return Err(CleanroomError::internal_error("Unknown test name")
102 .with_context(format!("Test name: {}", test_name)));
103 }
104 };
105
106 let test_duration = test_start.elapsed().as_millis() as u64;
107 let passed = test_result.is_ok();
108
109 if passed {
110 results.passed_tests += 1;
111 } else {
112 results.failed_tests += 1;
113 }
114
115 let error_msg = test_result.err().map(|e| e.to_string());
116
117 results.test_results.push(TestResult {
118 name: test_name.to_string(),
119 passed,
120 duration_ms: test_duration,
121 error: error_msg,
122 });
123 }
124
125 results.total_duration_ms = start_time.elapsed().as_millis() as u64;
126
127 Ok(results)
129}
130
131pub async fn validate_framework() -> Result<()> {
133 let policy = Policy::with_security_level(SecurityLevel::High);
135 policy.validate()
136 .map_err(|e| CleanroomError::internal_error("Policy validation failed")
137 .with_context("Core policy module validation")
138 .with_source(e.to_string()))?;
139
140 if !TestcontainerBackend::is_available() {
142 return Err(CleanroomError::internal_error("Testcontainers backend not available")
143 .with_context("Backend availability check failed"));
144 }
145
146 let _test_scenario = scenario("framework_validation_test")
148 .step("validate".to_string(), ["echo", "framework validation successful"]);
149
150 let test_error = CleanroomError::validation_error("Test error")
155 .with_context("Framework validation test")
156 .with_source("test_source");
157
158 if test_error.message != "Test error" {
159 return Err(CleanroomError::internal_error("Error type validation failed")
160 .with_context("Error message not preserved"));
161 }
162
163 let _env = CleanroomEnvironment::new().await
165 .map_err(|e| CleanroomError::internal_error("Failed to create cleanroom environment")
166 .with_context("Environment creation validation")
167 .with_source(e.to_string()))?;
168
169 Ok(())
170}
171
172pub async fn test_container_lifecycle() -> Result<()> {
174 let backend = TestcontainerBackend::new("alpine:latest")
176 .map_err(|e| CleanroomError::internal_error("Failed to create testcontainer backend")
177 .with_context("Container lifecycle test initialization")
178 .with_source(e.to_string()))?;
179
180 if backend.name() != "testcontainers" {
182 return Err(CleanroomError::internal_error("Backend name validation failed")
183 .with_context("Expected 'testcontainers' backend name"));
184 }
185
186 if !backend.is_available() {
187 return Err(CleanroomError::internal_error("Backend availability check failed")
188 .with_context("Backend should be available"));
189 }
190
191 if !backend.supports_hermetic() {
192 return Err(CleanroomError::internal_error("Backend hermetic support check failed")
193 .with_context("Backend should support hermetic execution"));
194 }
195
196 if !backend.supports_deterministic() {
197 return Err(CleanroomError::internal_error("Backend deterministic support check failed")
198 .with_context("Backend should support deterministic execution"));
199 }
200
201 let cmd = Cmd::new("echo")
203 .arg("hello world")
204 .env("TEST_VAR", "test_value");
205
206 if cmd.bin != "echo" {
207 return Err(CleanroomError::internal_error("Command binary validation failed")
208 .with_context("Expected 'echo' binary"));
209 }
210
211 if cmd.args.len() != 1 || cmd.args[0] != "hello world" {
212 return Err(CleanroomError::internal_error("Command arguments validation failed")
213 .with_context("Expected one argument 'hello world'"));
214 }
215
216 if cmd.env.get("TEST_VAR") != Some(&"test_value".to_string()) {
217 return Err(CleanroomError::internal_error("Command environment validation failed")
218 .with_context("Expected TEST_VAR=test_value"));
219 }
220
221 Ok(())
225}
226
227pub async fn test_plugin_system() -> Result<()> {
229 let test_plugin = TestServicePlugin::new("test_service");
231
232 if test_plugin.name() != "test_service" {
234 return Err(CleanroomError::internal_error("Plugin name validation failed")
235 .with_context("Plugin name not preserved"));
236 }
237
238 let handle = test_plugin.start().await
240 .map_err(|e| CleanroomError::internal_error("Service start failed")
241 .with_context("Plugin service start test")
242 .with_source(e.to_string()))?;
243
244 if handle.service_name != "test_service" {
245 return Err(CleanroomError::internal_error("Service handle validation failed")
246 .with_context("Service name not preserved in handle"));
247 }
248
249 let health = test_plugin.health_check(&handle);
251 if health != HealthStatus::Healthy {
252 return Err(CleanroomError::internal_error("Health check validation failed")
253 .with_context("Expected healthy status"));
254 }
255
256 test_plugin.stop(handle).await
258 .map_err(|e| CleanroomError::internal_error("Service stop failed")
259 .with_context("Plugin service stop test")
260 .with_source(e.to_string()))?;
261
262 let environment = CleanroomEnvironment::new().await
264 .map_err(|e| CleanroomError::internal_error("Failed to create cleanroom environment")
265 .with_context("Plugin system test environment")
266 .with_source(e.to_string()))?;
267
268 let test_plugin: Box<dyn ServicePlugin> = Box::new(TestServicePlugin::new("env_test_service"));
271 environment.register_service(test_plugin).await
272 .map_err(|e| CleanroomError::internal_error("Service registration failed")
273 .with_context("Environment service registration test")
274 .with_source(e.to_string()))?;
275
276 let env_handle = environment.start_service("env_test_service").await
278 .map_err(|e| CleanroomError::internal_error("Environment service start failed")
279 .with_context("Environment service start test")
280 .with_source(e.to_string()))?;
281
282 if env_handle.service_name != "env_test_service" {
283 return Err(CleanroomError::internal_error("Environment service handle validation failed")
284 .with_context("Service name not preserved in environment handle"));
285 }
286
287 environment.stop_service(&env_handle.id).await
289 .map_err(|e| CleanroomError::internal_error("Environment service stop failed")
290 .with_context("Environment service cleanup test")
291 .with_source(e.to_string()))?;
292
293 Ok(())
294}
295
296pub async fn test_cli_functionality() -> Result<()> {
298 list_plugins()
300 .map_err(|e| CleanroomError::internal_error("List plugins failed")
301 .with_context("CLI plugins listing test")
302 .with_source(e.to_string()))?;
303
304 let temp_dir = TempDir::new()
306 .map_err(|e| CleanroomError::internal_error("Failed to create temporary directory")
307 .with_context("CLI init project test setup")
308 .with_source(e.to_string()))?;
309
310 let original_dir = std::env::current_dir()
312 .map_err(|e| CleanroomError::internal_error("Failed to get current directory")
313 .with_context("CLI init project test directory change")
314 .with_source(e.to_string()))?;
315
316 std::env::set_current_dir(temp_dir.path())
317 .map_err(|e| CleanroomError::internal_error("Failed to change to test directory")
318 .with_context("CLI init project test directory change")
319 .with_source(e.to_string()))?;
320
321 let init_result = init_project(Some("test_project"), "default");
323
324 std::env::set_current_dir(&original_dir)
326 .map_err(|e| CleanroomError::internal_error("Failed to restore original directory")
327 .with_context("CLI init project test cleanup")
328 .with_source(e.to_string()))?;
329
330 init_result
331 .map_err(|e| CleanroomError::internal_error("Init project failed")
332 .with_context("CLI init project test")
333 .with_source(e.to_string()))?;
334
335 let sample_toml = r#"
337[test]
338name = "sample_test"
339description = "A sample test configuration"
340
341[services]
342
343[[steps]]
344name = "basic_step"
345command = ["echo", "test scenario"]
346"#;
347
348 let temp_file = temp_dir.path().join("sample_test.toml");
349 std::fs::write(&temp_file, sample_toml)
350 .map_err(|e| CleanroomError::internal_error("Failed to write sample TOML file")
351 .with_context("CLI validation test setup")
352 .with_source(e.to_string()))?;
353
354 validate_config(&temp_file)
355 .map_err(|e| CleanroomError::internal_error("Config validation failed")
356 .with_context("CLI config validation test")
357 .with_source(e.to_string()))?;
358
359 let invalid_toml = "invalid toml content [unclosed bracket";
361 let invalid_file = temp_dir.path().join("invalid_test.toml");
362 std::fs::write(&invalid_file, invalid_toml)
363 .map_err(|e| CleanroomError::internal_error("Failed to write invalid TOML file")
364 .with_context("CLI error handling test setup")
365 .with_source(e.to_string()))?;
366
367 let validation_error = validate_config(&invalid_file);
368 if validation_error.is_ok() {
369 return Err(CleanroomError::internal_error("Error handling test failed")
370 .with_context("Invalid TOML should have failed validation"));
371 }
372
373 Ok(())
374}
375
376pub async fn test_otel_integration() -> Result<()> {
378 #[cfg(all(feature = "otel-traces", feature = "otel-stdout"))]
380 {
381 use crate::telemetry::{OtelConfig, Export};
382
383 let otel_config = OtelConfig {
384 service_name: "clnrm-test",
385 deployment_env: "test",
386 sample_ratio: 1.0,
387 export: Export::Stdout,
388 enable_fmt_layer: false,
389 };
390
391 let _otel_guard = crate::telemetry::init_otel(otel_config);
393 }
394
395 #[cfg(not(all(feature = "otel-traces", feature = "otel-stdout")))]
396 {
397 }
400
401 let environment = CleanroomEnvironment::new().await
403 .map_err(|e| CleanroomError::internal_error("Failed to create cleanroom environment")
404 .with_context("OTel integration test environment")
405 .with_source(e.to_string()))?;
406
407 let test_result = environment.execute_test("otel_integration_test", || {
409 Ok::<i32, CleanroomError>(42)
411 }).await
412 .map_err(|e| CleanroomError::internal_error("OTel traced test execution failed")
413 .with_context("OTel integration test execution")
414 .with_source(e.to_string()))?;
415
416 if test_result != 42 {
417 return Err(CleanroomError::internal_error("OTel traced test result validation failed")
418 .with_context("Expected test result 42"));
419 }
420
421 let metrics = environment.get_metrics().await?;
423 if metrics.tests_executed == 0 {
424 return Err(CleanroomError::internal_error("OTel metrics collection failed")
425 .with_context("No tests recorded in metrics"));
426 }
427
428 let _traced_scenario = scenario("otel_traced_scenario")
430 .step("traced_step".to_string(), ["echo", "otel integration test"]);
431
432 Ok(())
436}
437
438struct TestServicePlugin {
440 name: String,
441}
442
443impl TestServicePlugin {
444 fn new(name: &str) -> Self {
445 Self {
446 name: name.to_string(),
447 }
448 }
449}
450
451impl ServicePlugin for TestServicePlugin {
452 fn name(&self) -> &str {
453 &self.name
454 }
455
456 fn start(&self) -> Pin<Box<dyn Future<Output = Result<ServiceHandle>> + Send + '_>> {
457 Box::pin(async move {
458 Ok(ServiceHandle {
459 id: format!("test_{}", uuid::Uuid::new_v4()),
460 service_name: self.name.clone(),
461 metadata: HashMap::from([
462 ("type".to_string(), "test".to_string()),
463 ("status".to_string(), "running".to_string()),
464 ]),
465 })
466 })
467 }
468
469 fn stop(&self, _handle: ServiceHandle) -> Pin<Box<dyn Future<Output = Result<()>> + Send + '_>> {
470 Box::pin(async move {
471 Ok(())
473 })
474 }
475
476 fn health_check(&self, _handle: &ServiceHandle) -> HealthStatus {
477 HealthStatus::Healthy
478 }
479}
480
481#[cfg(test)]
482mod tests {
483 use super::*;
484
485 #[tokio::test]
486 async fn test_run_framework_tests() -> Result<()> {
487 let result = run_framework_tests().await;
488 assert!(result.is_ok(), "Framework self-tests should succeed: {:?}", result.err());
489
490 let test_results = result.map_err(|e|
491 CleanroomError::internal_error("Framework tests failed")
492 .with_context("Failed to execute framework test suite")
493 .with_source(e.to_string())
494 )?;
495 assert!(test_results.total_tests > 0, "Should have executed some tests");
496 assert_eq!(test_results.total_tests, test_results.passed_tests + test_results.failed_tests);
497 assert!(test_results.total_duration_ms > 0, "Should have recorded execution time");
498 Ok(())
499 }
500
501 #[tokio::test]
502 async fn test_validate_framework() {
503 let result = validate_framework().await;
504 assert!(result.is_ok(), "Framework validation should succeed: {:?}", result.err());
505 }
506
507 #[tokio::test]
508 async fn test_container_lifecycle_individual() {
509 let result = test_container_lifecycle().await;
510 assert!(result.is_ok(), "Container lifecycle test should succeed: {:?}", result.err());
511 }
512
513 #[tokio::test]
514 async fn test_plugin_system_individual() {
515 let result = test_plugin_system().await;
516 assert!(result.is_ok(), "Plugin system test should succeed: {:?}", result.err());
517 }
518
519 #[tokio::test]
520 async fn test_cli_functionality_individual() {
521 let result = test_cli_functionality().await;
522 assert!(result.is_ok(), "CLI functionality test should succeed: {:?}", result.err());
523 }
524
525 #[tokio::test]
526 async fn test_otel_integration_individual() {
527 let result = test_otel_integration().await;
528 assert!(result.is_ok(), "OTel integration test should succeed: {:?}", result.err());
529 }
530
531 #[tokio::test]
532 async fn test_framework_test_results_structure() {
533 let results = FrameworkTestResults {
534 total_tests: 5,
535 passed_tests: 4,
536 failed_tests: 1,
537 total_duration_ms: 1000,
538 test_results: vec![
539 TestResult {
540 name: "test1".to_string(),
541 passed: true,
542 duration_ms: 200,
543 error: None,
544 },
545 TestResult {
546 name: "test2".to_string(),
547 passed: false,
548 duration_ms: 300,
549 error: Some("Test failed".to_string()),
550 },
551 ],
552 };
553
554 assert_eq!(results.total_tests, 5);
555 assert_eq!(results.passed_tests, 4);
556 assert_eq!(results.failed_tests, 1);
557 assert_eq!(results.test_results.len(), 2);
558 }
559
560 #[tokio::test]
561 async fn test_test_service_plugin() -> Result<()> {
562 let plugin = TestServicePlugin::new("test_plugin");
563 assert_eq!(plugin.name(), "test_plugin");
564
565 let handle = plugin.start().await
566 .map_err(|e| CleanroomError::internal_error("Plugin start failed")
567 .with_context("Test service plugin startup failed")
568 .with_source(e.to_string())
569 )?;
570 assert_eq!(handle.service_name, "test_plugin");
571 assert!(handle.id.starts_with("test_"));
572
573 let health = plugin.health_check(&handle);
574 assert_eq!(health, HealthStatus::Healthy);
575
576 let stop_result = plugin.stop(handle).await;
577 assert!(stop_result.is_ok());
578 Ok(())
579 }
580}