Skip to main content

xchecker_engine/
integration_tests.rs

1//! Integration tests for final validation of all components working together
2//!
3//! This module provides smoke tests and validation to ensure all systems
4//! are properly integrated and working as expected.
5
6use crate::runner::CommandSpec;
7use anyhow::{Context, Result};
8use tempfile::TempDir;
9
10/// Run basic smoke tests to validate integration
11pub fn run_smoke_tests() -> Result<()> {
12    println!("Running integration smoke tests...");
13
14    // Test 1: CLI help works
15    test_cli_help()?;
16
17    // Test 2: Version information works
18    test_version_info()?;
19
20    // Test 3: Dry-run execution works
21    test_dry_run_execution()?;
22
23    // Test 4: Status command works
24    test_status_command()?;
25
26    // Test 5: Benchmark command works
27    test_benchmark_command()?;
28
29    println!("✓ All smoke tests passed");
30    Ok(())
31}
32
33/// Test that CLI help works correctly
34fn test_cli_help() -> Result<()> {
35    let output = CommandSpec::new("cargo")
36        .args(["run", "--bin", "xchecker", "--", "--help"])
37        .to_command()
38        .output()?;
39
40    if !output.status.success() {
41        anyhow::bail!("CLI help command failed");
42    }
43
44    let help_text = String::from_utf8_lossy(&output.stdout);
45    if !help_text.contains("xchecker") || !help_text.contains("spec") {
46        anyhow::bail!("CLI help output doesn't contain expected content");
47    }
48
49    println!("✓ CLI help works");
50    Ok(())
51}
52
53/// Test that version information works
54fn test_version_info() -> Result<()> {
55    let output = CommandSpec::new("cargo")
56        .args(["run", "--bin", "xchecker", "--", "--version"])
57        .to_command()
58        .output()?;
59
60    if !output.status.success() {
61        anyhow::bail!("Version command failed");
62    }
63
64    let version_text = String::from_utf8_lossy(&output.stdout);
65    if version_text.trim().is_empty() {
66        anyhow::bail!("Version output is empty");
67    }
68
69    println!("✓ Version info works");
70    Ok(())
71}
72
73/// Test that dry-run execution works end-to-end
74fn test_dry_run_execution() -> Result<()> {
75    let _temp_dir = TempDir::new()?;
76
77    let mut cmd = CommandSpec::new("cargo")
78        .args([
79            "run",
80            "--bin",
81            "xchecker",
82            "--",
83            "spec",
84            "smoke-test",
85            "--dry-run",
86        ])
87        .to_command();
88
89    // Provide test input via stdin
90    let mut child = cmd
91        .stdin(std::process::Stdio::piped())
92        .stdout(std::process::Stdio::piped())
93        .stderr(std::process::Stdio::piped())
94        .spawn()?;
95
96    if let Some(stdin) = child.stdin.as_mut() {
97        use std::io::Write;
98        writeln!(stdin, "Test spec: Create a simple calculator application")?;
99        let _: std::io::Result<()> = std::io::Result::Ok(());
100    }
101
102    let output = child.wait_with_output()?;
103
104    if !output.status.success() {
105        let stderr = String::from_utf8_lossy(&output.stderr);
106        anyhow::bail!("Dry-run execution failed: {stderr}");
107    }
108
109    let stdout = String::from_utf8_lossy(&output.stdout);
110    if !stdout.contains("Requirements phase completed successfully") {
111        anyhow::bail!("Dry-run didn't complete requirements phase");
112    }
113
114    println!("✓ Dry-run execution works");
115    Ok(())
116}
117
118/// Test that status command works
119fn test_status_command() -> Result<()> {
120    let output = CommandSpec::new("cargo")
121        .args([
122            "run",
123            "--bin",
124            "xchecker",
125            "--",
126            "status",
127            "nonexistent-spec",
128        ])
129        .to_command()
130        .output()?;
131
132    if !output.status.success() {
133        anyhow::bail!("Status command failed");
134    }
135
136    let status_text = String::from_utf8_lossy(&output.stdout);
137    if !status_text.contains("Status for spec") {
138        anyhow::bail!("Status output doesn't contain expected content");
139    }
140
141    println!("✓ Status command works");
142    Ok(())
143}
144
145/// Test that benchmark command works
146fn test_benchmark_command() -> Result<()> {
147    // Test 1: Basic benchmark command
148    let output = CommandSpec::new("cargo")
149        .args([
150            "run",
151            "--bin",
152            "xchecker",
153            "--",
154            "benchmark",
155            "--file-count",
156            "10",
157            "--iterations",
158            "2",
159        ])
160        .to_command()
161        .output()?;
162
163    if !output.status.success() {
164        let stderr = String::from_utf8_lossy(&output.stderr);
165        anyhow::bail!("Benchmark command failed: {stderr}");
166    }
167
168    let benchmark_text = String::from_utf8_lossy(&output.stdout);
169    if !benchmark_text.contains("Benchmark Results") {
170        anyhow::bail!("Benchmark output doesn't contain expected content");
171    }
172
173    // Test 2: JSON output format
174    let json_output = CommandSpec::new("cargo")
175        .args([
176            "run",
177            "--bin",
178            "xchecker",
179            "--",
180            "benchmark",
181            "--file-count",
182            "5",
183            "--iterations",
184            "2",
185            "--json",
186        ])
187        .to_command()
188        .output()?;
189
190    if !json_output.status.success() {
191        let stderr = String::from_utf8_lossy(&json_output.stderr);
192        anyhow::bail!("Benchmark JSON command failed: {stderr}");
193    }
194
195    let json_text = String::from_utf8_lossy(&json_output.stdout);
196
197    // Parse JSON to validate structure
198    let json_value: serde_json::Value =
199        serde_json::from_str(&json_text).context("Failed to parse benchmark JSON output")?;
200
201    // Validate required fields (FR-BENCH-004)
202    if json_value.get("ok").is_none() {
203        anyhow::bail!("JSON output missing 'ok' field");
204    }
205    if json_value.get("timings_ms").is_none() {
206        anyhow::bail!("JSON output missing 'timings_ms' field");
207    }
208    if json_value.get("rss_mb").is_none() {
209        anyhow::bail!("JSON output missing 'rss_mb' field");
210    }
211
212    // Test 3: Threshold overrides
213    let threshold_output = CommandSpec::new("cargo")
214        .args([
215            "run",
216            "--bin",
217            "xchecker",
218            "--",
219            "benchmark",
220            "--file-count",
221            "5",
222            "--iterations",
223            "2",
224            "--max-empty-run-secs",
225            "10.0",
226            "--max-packetization-ms",
227            "500.0",
228            "--json",
229        ])
230        .to_command()
231        .output()?;
232
233    if !threshold_output.status.success() {
234        let stderr = String::from_utf8_lossy(&threshold_output.stderr);
235        anyhow::bail!("Benchmark threshold override command failed: {stderr}");
236    }
237
238    let threshold_json_text = String::from_utf8_lossy(&threshold_output.stdout);
239    let threshold_json: serde_json::Value = serde_json::from_str(&threshold_json_text)
240        .context("Failed to parse threshold override JSON output")?;
241
242    // Validate thresholds were applied
243    if let Some(thresholds) = threshold_json.get("thresholds") {
244        if let Some(empty_run_max) = thresholds.get("empty_run_max_secs") {
245            if empty_run_max.as_f64() != Some(10.0) {
246                anyhow::bail!("Threshold override for empty_run_max_secs not applied correctly");
247            }
248        } else {
249            anyhow::bail!("Thresholds missing empty_run_max_secs field");
250        }
251
252        if let Some(packet_max) = thresholds.get("packetization_max_ms_per_100_files") {
253            if packet_max.as_f64() != Some(500.0) {
254                anyhow::bail!(
255                    "Threshold override for packetization_max_ms_per_100_files not applied correctly"
256                );
257            }
258        } else {
259            anyhow::bail!("Thresholds missing packetization_max_ms_per_100_files field");
260        }
261    } else {
262        anyhow::bail!("JSON output missing 'thresholds' field");
263    }
264
265    println!("✓ Benchmark command works (basic, JSON, and threshold overrides)");
266    Ok(())
267}
268
269/// Validate that all required components are properly integrated
270pub fn validate_component_integration() -> Result<()> {
271    println!("Validating component integration...");
272
273    // Check that all modules are properly exported in lib.rs
274    validate_module_exports()?;
275
276    // Check that error handling is properly integrated
277    validate_error_integration()?;
278
279    // Check that configuration system is working
280    validate_config_integration()?;
281
282    println!("✓ Component integration validated");
283    Ok(())
284}
285
286/// Validate that all modules are properly exported
287fn validate_module_exports() -> Result<()> {
288    // This is a compile-time check - if the code compiles, exports are working
289    use crate::types::{FileType, PhaseId, RunnerMode};
290
291    // Test that we can create key types
292    let _phase_id = PhaseId::Requirements;
293    let _file_type = FileType::Markdown;
294    let _runner_mode = RunnerMode::Auto;
295
296    println!("✓ Module exports validated");
297    Ok(())
298}
299
300/// Validate error handling integration
301fn validate_error_integration() -> Result<()> {
302    use crate::error::{ConfigError, XCheckerError};
303
304    // Test that we can create and handle errors
305    let _error = XCheckerError::Config(ConfigError::MissingRequired("test".to_string()));
306
307    println!("✓ Error integration validated");
308    Ok(())
309}
310
311/// Validate configuration integration
312fn validate_config_integration() -> Result<()> {
313    // Test that configuration system is accessible
314    // This is mainly a compile-time check
315    println!("✓ Configuration system accessible");
316
317    println!("✓ Configuration integration validated");
318    Ok(())
319}
320
321#[cfg(test)]
322mod tests {
323    use super::*;
324
325    #[test]
326    fn test_component_integration() {
327        validate_component_integration().expect("Component integration validation failed");
328    }
329
330    #[test]
331    fn test_module_exports() {
332        validate_module_exports().expect("Module export validation failed");
333    }
334
335    #[test]
336    fn test_error_integration() {
337        validate_error_integration().expect("Error integration validation failed");
338    }
339
340    #[test]
341    fn test_config_integration() {
342        validate_config_integration().expect("Config integration validation failed");
343    }
344}