Coverage Report

Created: 2025-09-05 15:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/home/noah/src/ruchy/src/quality/enforcement.rs
Line
Count
Source
1
//! Quality gate enforcement implementation for CLI
2
3
use std::path::Path;
4
use anyhow::Result;
5
use crate::quality::gates::{QualityGateEnforcer, QualityGateConfig};
6
use crate::quality::scoring::{ScoreEngine, AnalysisDepth};
7
8
/// Enforce quality gates on a file or directory
9
20
pub fn enforce_quality_gates(
10
20
    path: &Path,
11
20
    config: Option<&Path>,
12
20
    depth: &str,
13
20
    fail_fast: bool,
14
20
    format: &str,
15
20
    export: Option<&Path>,
16
20
    ci: bool,
17
20
    verbose: bool,
18
20
) -> Result<()> {
19
    // Load configuration
20
20
    let project_root = find_project_root(path)
?0
;
21
20
    let 
mut gate_config19
= if let Some(
config_path1
) = config {
22
1
        QualityGateEnforcer::load_config(config_path.parent().unwrap_or(Path::new(".")))?
23
    } else {
24
19
        QualityGateEnforcer::load_config(&project_root)
?0
25
    };
26
    
27
    // Apply CI mode overrides (stricter thresholds)
28
19
    if ci {
29
0
        gate_config = apply_ci_overrides(gate_config);
30
19
    }
31
    
32
19
    let enforcer = QualityGateEnforcer::new(gate_config);
33
    
34
    // Parse analysis depth
35
19
    let 
analysis_depth17
= match depth {
36
19
        "shallow" => 
AnalysisDepth::Shallow1
,
37
18
        "standard" => 
AnalysisDepth::Standard15
,
38
3
        "deep" => 
AnalysisDepth::Deep1
,
39
2
        _ => return Err(anyhow::anyhow!("Invalid depth: {}", depth)),
40
    };
41
    
42
    // Process file or directory
43
17
    let mut all_results = Vec::new();
44
    
45
17
    if path.is_file() {
46
13
        let 
result12
= process_file(&enforcer, path, analysis_depth, verbose)
?1
;
47
12
        all_results.push(result);
48
4
    } else if path.is_dir() {
49
3
        let results = process_directory(&enforcer, path, analysis_depth, fail_fast, verbose)
?0
;
50
3
        all_results.extend(results);
51
    } else {
52
1
        return Err(anyhow::anyhow!("Invalid path: {}", path.display()));
53
    }
54
    
55
    // Output results
56
15
    match format {
57
15
        "console" => 
print_console_results11
(
&all_results11
,
verbose11
)
?0
,
58
4
        "json" => 
print_json_results1
(
&all_results1
)
?0
,
59
3
        "junit" => 
print_junit_results1
(
&all_results1
)
?0
,
60
2
        _ => return Err(anyhow::anyhow!("Invalid format: {}", format)),
61
    }
62
    
63
    // Export CI results if requested
64
13
    if let Some(
export_path1
) = export {
65
1
        std::fs::create_dir_all(export_path)
?0
;
66
1
        enforcer.export_ci_results(&all_results, export_path)
?0
;
67
1
        println!("šŸ“Š Results exported to {}", export_path.display());
68
12
    }
69
    
70
    // Check if any gates failed
71
22
    let 
failed_gates13
=
all_results.iter()13
.
filter13
(|r| !r.passed).
count13
();
72
    
73
13
    if failed_gates > 0 {
74
0
        eprintln!("āŒ {failed_gates} quality gate(s) failed");
75
0
        std::process::exit(1);
76
13
    } else {
77
13
        println!("āœ… All quality gates passed!");
78
13
    }
79
    
80
13
    Ok(())
81
20
}
82
83
25
fn find_project_root(path: &Path) -> Result<std::path::PathBuf> {
84
25
    let mut current = if path.is_file() {
85
18
        path.parent().unwrap_or(Path::new("."))
86
    } else {
87
7
        path
88
    };
89
    
90
    loop {
91
33
        if current.join("Cargo.toml").exists() || 
current.join(".ruchy").exists()11
{
92
23
            return Ok(current.to_path_buf());
93
10
        }
94
        
95
10
        if let Some(
parent8
) = current.parent() {
96
8
            current = parent;
97
8
        } else {
98
            // Default to current directory
99
2
            return Ok(Path::new(".").to_path_buf());
100
        }
101
    }
102
25
}
103
104
2
fn apply_ci_overrides(mut config: QualityGateConfig) -> QualityGateConfig {
105
    // Apply stricter thresholds for CI
106
2
    config.min_score = config.min_score.max(0.8); // Higher overall score
107
2
    config.component_thresholds.correctness = config.component_thresholds.correctness.max(0.9);
108
2
    config.component_thresholds.safety = config.component_thresholds.safety.max(0.9);
109
2
    config.anti_gaming.min_confidence = config.anti_gaming.min_confidence.max(0.8);
110
2
    config.ci_integration.fail_on_violation = true;
111
2
    config
112
2
}
113
114
25
fn process_file(
115
25
    enforcer: &QualityGateEnforcer,
116
25
    file_path: &Path,
117
25
    depth: AnalysisDepth,
118
25
    verbose: bool,
119
25
) -> Result<crate::quality::gates::GateResult> {
120
25
    if verbose {
121
1
        println!("šŸ” Analyzing {}", file_path.display());
122
24
    }
123
    
124
    // Read and parse file
125
25
    let content = std::fs::read_to_string(file_path)
?0
;
126
25
    let mut parser = crate::Parser::new(&content);
127
25
    let 
ast24
= parser.parse()
?1
;
128
    
129
    // Calculate score
130
24
    let score_config = crate::quality::scoring::ScoreConfig::default();
131
24
    let mut score_engine = ScoreEngine::new(score_config);
132
24
    let score = score_engine.score_incremental(&ast, file_path.to_path_buf(), &content, depth);
133
    
134
    // Enforce gates
135
24
    let result = enforcer.enforce_gates(&score, Some(&file_path.to_path_buf()));
136
    
137
24
    Ok(result)
138
25
}
139
140
3
fn process_directory(
141
3
    enforcer: &QualityGateEnforcer,
142
3
    dir_path: &Path,
143
3
    depth: AnalysisDepth,
144
3
    fail_fast: bool,
145
3
    verbose: bool,
146
3
) -> Result<Vec<crate::quality::gates::GateResult>> {
147
    use std::fs;
148
    
149
3
    let mut results = Vec::new();
150
    
151
    // Find all Ruchy files
152
18
    for entry in 
fs::read_dir3
(
dir_path3
)
?0
{
153
18
        let entry = entry
?0
;
154
18
        let path = entry.path();
155
        
156
18
        if path.is_file() && 
path.extension()15
.
is_some_and15
(|ext|
ext15
==
"ruchy"15
) {
157
12
            match process_file(enforcer, &path, depth, verbose) {
158
12
                Ok(result) => {
159
12
                    if fail_fast && 
!result.passed4
{
160
0
                        eprintln!("āŒ Failed fast on {}", path.display());
161
0
                        return Ok(vec![result]);
162
12
                    }
163
12
                    results.push(result);
164
                }
165
0
                Err(e) => {
166
0
                    eprintln!("āš ļø Error processing {}: {}", path.display(), e);
167
0
                    if fail_fast {
168
0
                        return Err(e);
169
0
                    }
170
                }
171
            }
172
6
        } else if path.is_dir() && 
!path.file_name().unwrap_or_default().to_string_lossy().starts_with('.')3
{
173
            // Recursively process subdirectories
174
0
            let subdir_results = process_directory(enforcer, &path, depth, fail_fast, verbose)?;
175
0
            results.extend(subdir_results);
176
6
        }
177
    }
178
    
179
3
    Ok(results)
180
3
}
181
182
11
fn print_console_results(results: &[crate::quality::gates::GateResult], verbose: bool) -> Result<()> {
183
20
    for (i, result) in 
results11
.
iter11
().
enumerate11
() {
184
20
        println!("\nšŸ“‹ Quality Gate #{}: {}", i + 1, if result.passed { "āœ… PASSED" } else { 
"āŒ FAILED"0
});
185
20
        println!("   Score: {:.1}% ({})", result.score * 100.0, result.grade);
186
20
        println!("   Confidence: {:.1}%", result.confidence * 100.0);
187
        
188
20
        if !result.violations.is_empty() {
189
18
            println!("   Violations:");
190
36
            for 
violation18
in &result.violations {
191
18
                println!("     • {}", violation.message);
192
18
                if verbose {
193
1
                    println!("       Type: {:?}, Severity: {:?}", violation.violation_type, violation.severity);
194
1
                    println!("       Required: {:.3}, Actual: {:.3}", violation.required, violation.actual);
195
17
                }
196
            }
197
2
        }
198
        
199
20
        if !result.gaming_warnings.is_empty() {
200
20
            println!("   Warnings:");
201
40
            for 
warning20
in &result.gaming_warnings {
202
20
                println!("     āš ļø {warning}");
203
20
            }
204
0
        }
205
    }
206
    
207
11
    let passed = results.iter().filter(|r| r.passed).count();
208
11
    let total = results.len();
209
    
210
11
    println!("\nšŸ“Š Summary: {passed}/{total} gates passed");
211
    
212
11
    Ok(())
213
11
}
214
215
1
fn print_json_results(results: &[crate::quality::gates::GateResult]) -> Result<()> {
216
1
    let json = serde_json::to_string_pretty(results)
?0
;
217
1
    println!("{json}");
218
1
    Ok(())
219
1
}
220
221
1
fn print_junit_results(results: &[crate::quality::gates::GateResult]) -> Result<()> {
222
1
    let total = results.len();
223
1
    let failures = results.iter().filter(|r| !r.passed).count();
224
    
225
1
    println!(r#"<?xml version="1.0" encoding="UTF-8"?>"#);
226
1
    println!(r#"<testsuite name="Quality Gates" tests="{total}" failures="{failures}" time="0.0">"#);
227
    
228
1
    for (i, result) in results.iter().enumerate() {
229
1
        let test_name = format!("quality-gate-{i}");
230
1
        if result.passed {
231
1
            println!(r#"  <testcase name="{test_name}" classname="QualityGate" time="0.0"/>"#);
232
1
        } else {
233
0
            println!(r#"  <testcase name="{test_name}" classname="QualityGate" time="0.0">"#);
234
0
            println!(r#"    <failure message="Quality gate violation">Score: {:.1}%, Grade: {}</failure>"#, 
235
0
                result.score * 100.0, result.grade);
236
0
            println!(r"  </testcase>");
237
0
        }
238
    }
239
    
240
1
    println!("</testsuite>");
241
1
    Ok(())
242
1
}
243
244
#[cfg(test)]
245
mod tests {
246
    use super::*;
247
    use tempfile::TempDir;
248
    use std::fs;
249
    use crate::quality::gates::QualityGateConfig;
250
251
31
    fn create_test_ruchy_file(dir: &Path, filename: &str, content: &str) -> std::path::PathBuf {
252
31
        let file_path = dir.join(filename);
253
31
        fs::write(&file_path, content).unwrap();
254
31
        file_path
255
31
    }
256
257
9
    fn create_test_project_structure(dir: &Path) -> std::path::PathBuf {
258
        // Create Cargo.toml to mark as project root
259
9
        fs::write(dir.join("Cargo.toml"), "[package]\nname = \"test\"\nversion = \"0.1.0\"").unwrap();
260
        
261
        // Create .ruchy directory
262
9
        fs::create_dir_all(dir.join(".ruchy")).unwrap();
263
        
264
        // Create some test Ruchy files
265
9
        create_test_ruchy_file(dir, "test.ruchy", "let x = 5\nprintln(x)");
266
9
        create_test_ruchy_file(dir, "simple.ruchy", "println(\"hello\")");
267
        
268
9
        dir.to_path_buf()
269
9
    }
270
271
    // Test 1: Project Root Finding
272
    #[test]
273
1
    fn test_find_project_root_with_cargo_toml() {
274
1
        let temp_dir = TempDir::new().unwrap();
275
1
        let project_dir = temp_dir.path();
276
        
277
        // Create Cargo.toml
278
1
        fs::write(project_dir.join("Cargo.toml"), "[package]").unwrap();
279
        
280
1
        let found_root = find_project_root(project_dir).unwrap();
281
1
        assert_eq!(found_root, project_dir);
282
1
    }
283
284
    #[test]
285
1
    fn test_find_project_root_with_ruchy_dir() {
286
1
        let temp_dir = TempDir::new().unwrap();
287
1
        let project_dir = temp_dir.path();
288
        
289
        // Create .ruchy directory
290
1
        fs::create_dir_all(project_dir.join(".ruchy")).unwrap();
291
        
292
1
        let found_root = find_project_root(project_dir).unwrap();
293
1
        assert_eq!(found_root, project_dir);
294
1
    }
295
296
    #[test]
297
1
    fn test_find_project_root_from_file() {
298
1
        let temp_dir = TempDir::new().unwrap();
299
1
        let project_dir = temp_dir.path();
300
        
301
        // Create Cargo.toml
302
1
        fs::write(project_dir.join("Cargo.toml"), "[package]").unwrap();
303
        
304
        // Create a file in project
305
1
        let file_path = create_test_ruchy_file(project_dir, "test.ruchy", "let x = 5");
306
        
307
1
        let found_root = find_project_root(&file_path).unwrap();
308
1
        assert_eq!(found_root, project_dir);
309
1
    }
310
311
    #[test]
312
1
    fn test_find_project_root_nested() {
313
1
        let temp_dir = TempDir::new().unwrap();
314
1
        let project_dir = temp_dir.path();
315
        
316
        // Create nested directory structure
317
1
        let nested_dir = project_dir.join("src").join("deep");
318
1
        fs::create_dir_all(&nested_dir).unwrap();
319
        
320
        // Create Cargo.toml at root
321
1
        fs::write(project_dir.join("Cargo.toml"), "[package]").unwrap();
322
        
323
        // Create file in nested directory
324
1
        let file_path = create_test_ruchy_file(&nested_dir, "nested.ruchy", "println(\"nested\")");
325
        
326
1
        let found_root = find_project_root(&file_path).unwrap();
327
1
        assert_eq!(found_root, project_dir);
328
1
    }
329
330
    #[test]
331
1
    fn test_find_project_root_fallback() {
332
1
        let temp_dir = TempDir::new().unwrap();
333
1
        let some_dir = temp_dir.path().join("no_project_markers");
334
1
        fs::create_dir_all(&some_dir).unwrap();
335
        
336
1
        let found_root = find_project_root(&some_dir).unwrap();
337
        // Should fallback to current directory
338
1
        assert_eq!(found_root, Path::new("."));
339
1
    }
340
341
    // Test 2: CI Overrides
342
    #[test]
343
1
    fn test_apply_ci_overrides() {
344
1
        let mut config = QualityGateConfig::default();
345
        
346
        // Set lower initial values to test overrides
347
1
        config.min_score = 0.6;
348
1
        config.component_thresholds.correctness = 0.7;
349
1
        config.component_thresholds.safety = 0.7;
350
1
        config.anti_gaming.min_confidence = 0.5;
351
1
        config.ci_integration.fail_on_violation = false;
352
        
353
1
        let ci_config = apply_ci_overrides(config);
354
        
355
        // Should apply stricter thresholds
356
1
        assert!(ci_config.min_score >= 0.8);
357
1
        assert!(ci_config.component_thresholds.correctness >= 0.9);
358
1
        assert!(ci_config.component_thresholds.safety >= 0.9);
359
1
        assert!(ci_config.anti_gaming.min_confidence >= 0.8);
360
1
        assert!(ci_config.ci_integration.fail_on_violation);
361
1
    }
362
363
    #[test]
364
1
    fn test_ci_overrides_preserve_higher_values() {
365
1
        let mut config = QualityGateConfig::default();
366
        
367
        // Set higher initial values
368
1
        config.min_score = 0.95;
369
1
        config.component_thresholds.correctness = 0.95;
370
1
        config.component_thresholds.safety = 0.95;
371
1
        config.anti_gaming.min_confidence = 0.95;
372
        
373
1
        let ci_config = apply_ci_overrides(config);
374
        
375
        // Should preserve higher existing values
376
1
        assert_eq!(ci_config.min_score, 0.95);
377
1
        assert_eq!(ci_config.component_thresholds.correctness, 0.95);
378
1
        assert_eq!(ci_config.component_thresholds.safety, 0.95);
379
1
        assert_eq!(ci_config.anti_gaming.min_confidence, 0.95);
380
1
    }
381
382
    // Test 3: Analysis Depth Parsing
383
    #[test]
384
1
    fn test_analysis_depth_parsing() {
385
1
        let temp_dir = TempDir::new().unwrap();
386
1
        let project_dir = create_test_project_structure(temp_dir.path());
387
1
        let file_path = create_test_ruchy_file(&project_dir, "depth_test.ruchy", "let x = 1");
388
        
389
        // Test all valid depth values
390
1
        let depths = vec![
391
1
            ("shallow", true),
392
1
            ("standard", true), 
393
1
            ("deep", true),
394
1
            ("invalid", false),
395
1
            ("", false),
396
        ];
397
        
398
6
        for (
depth_str5
,
should_succeed5
) in depths {
399
5
            let result = enforce_quality_gates(
400
5
                &file_path,
401
5
                None,
402
5
                depth_str,
403
                false,
404
5
                "console",
405
5
                None,
406
                false,
407
                false,
408
            );
409
            
410
5
            if should_succeed {
411
                // For valid depths, should not fail on depth parsing
412
                // May fail on other quality issues, but not depth parsing
413
3
                if let Err(
e0
) = &result {
414
0
                    assert!(!e.to_string().contains("Invalid depth"), 
415
0
                        "Should not fail on depth parsing for '{}'", depth_str);
416
3
                }
417
            } else {
418
2
                assert!(result.is_err(), 
"Should fail for invalid depth: '{}'"0
, depth_str);
419
2
                if let Err(e) = result {
420
2
                    assert!(e.to_string().contains("Invalid depth"), 
421
0
                        "Should fail with depth error for '{}'", depth_str);
422
0
                }
423
            }
424
        }
425
1
    }
426
427
    // Test 4: Format Validation
428
    #[test] 
429
1
    fn test_format_validation() {
430
1
        let temp_dir = TempDir::new().unwrap();
431
1
        let project_dir = create_test_project_structure(temp_dir.path());
432
1
        let file_path = create_test_ruchy_file(&project_dir, "format_test.ruchy", "let x = 1");
433
        
434
1
        let formats = vec![
435
1
            ("console", true),
436
1
            ("json", true),
437
1
            ("junit", true),
438
1
            ("xml", false),
439
1
            ("invalid", false),
440
        ];
441
        
442
6
        for (
format5
,
should_succeed5
) in formats {
443
5
            let result = enforce_quality_gates(
444
5
                &file_path,
445
5
                None,
446
5
                "standard",
447
                false,
448
5
                format,
449
5
                None,
450
                false,
451
                false,
452
            );
453
            
454
5
            if should_succeed {
455
                // Valid formats shouldn't fail on format parsing
456
3
                if let Err(
e0
) = &result {
457
0
                    assert!(!e.to_string().contains("Invalid format"), 
458
0
                        "Should not fail on format parsing for '{}'", format);
459
3
                }
460
            } else {
461
2
                assert!(result.is_err(), 
"Should fail for invalid format: '{}'"0
, format);
462
2
                if let Err(e) = result {
463
2
                    assert!(e.to_string().contains("Invalid format"), 
464
0
                        "Should fail with format error for '{}'", format);
465
0
                }
466
            }
467
        }
468
1
    }
469
470
    // Test 5: File vs Directory Processing
471
    #[test]
472
1
    fn test_single_file_processing() {
473
1
        let temp_dir = TempDir::new().unwrap();
474
1
        let project_dir = create_test_project_structure(temp_dir.path());
475
1
        let file_path = create_test_ruchy_file(&project_dir, "single.ruchy", "println(\"test\")");
476
        
477
        // This should not crash and should process the single file
478
1
        let result = enforce_quality_gates(
479
1
            &file_path,
480
1
            None,
481
1
            "standard", 
482
            false,
483
1
            "console",
484
1
            None,
485
            false,
486
            false,
487
        );
488
        
489
        // May fail due to quality issues, but should not crash
490
1
        assert!(result.is_ok() || 
result0
.
is_err0
(),
"Should complete processing"0
);
491
1
    }
492
493
    #[test]
494
1
    fn test_directory_processing() {
495
1
        let temp_dir = TempDir::new().unwrap();
496
1
        let project_dir = create_test_project_structure(temp_dir.path());
497
        
498
        // Create multiple files
499
1
        create_test_ruchy_file(&project_dir, "file1.ruchy", "let a = 1");
500
1
        create_test_ruchy_file(&project_dir, "file2.ruchy", "let b = 2");
501
        
502
        // This should process directory
503
1
        let result = enforce_quality_gates(
504
1
            &project_dir,
505
1
            None,
506
1
            "standard",
507
            false,
508
1
            "console", 
509
1
            None,
510
            false,
511
            false,
512
        );
513
        
514
        // May fail due to quality issues, but should not crash
515
1
        assert!(result.is_ok() || 
result0
.
is_err0
(),
"Should complete directory processing"0
);
516
1
    }
517
518
    #[test]
519
1
    fn test_nonexistent_path() {
520
1
        let temp_dir = TempDir::new().unwrap();
521
1
        let nonexistent = temp_dir.path().join("does_not_exist.ruchy");
522
        
523
1
        let result = enforce_quality_gates(
524
1
            &nonexistent,
525
1
            None,
526
1
            "standard",
527
            false,
528
1
            "console",
529
1
            None,
530
            false,
531
            false,
532
        );
533
        
534
1
        assert!(result.is_err(), 
"Should fail for nonexistent path"0
);
535
1
    }
536
537
    // Test 6: Configuration Loading
538
    #[test]
539
1
    fn test_custom_config_loading() {
540
1
        let temp_dir = TempDir::new().unwrap();
541
1
        let project_dir = create_test_project_structure(temp_dir.path());
542
1
        let file_path = create_test_ruchy_file(&project_dir, "config_test.ruchy", "let x = 1");
543
        
544
        // Create custom config
545
1
        let config_dir = temp_dir.path().join("custom_config");
546
1
        fs::create_dir_all(&config_dir).unwrap();
547
1
        fs::create_dir_all(config_dir.join(".ruchy")).unwrap();
548
        
549
        // Create custom score.toml
550
1
        let config_content = r#"
551
1
min_score = 0.5
552
1
min_grade = "D"
553
1
554
1
[component_thresholds]
555
1
correctness = 0.4
556
1
performance = 0.4
557
1
maintainability = 0.4
558
1
safety = 0.4
559
1
idiomaticity = 0.4
560
1
"#;
561
1
        fs::write(config_dir.join(".ruchy").join("score.toml"), config_content).unwrap();
562
        
563
1
        let custom_config_path = config_dir.join("score.toml");
564
        
565
1
        let result = enforce_quality_gates(
566
1
            &file_path,
567
1
            Some(&custom_config_path),
568
1
            "standard",
569
            false,
570
1
            "console",
571
1
            None,
572
            false,
573
            false,
574
        );
575
        
576
        // Should use custom config (may pass due to lower thresholds)
577
1
        assert!(result.is_ok() || result.is_err(), 
"Should process with custom config"0
);
578
1
    }
579
580
    // Test 7: Export Functionality
581
    #[test]
582
1
    fn test_export_directory_creation() {
583
1
        let temp_dir = TempDir::new().unwrap();
584
1
        let project_dir = create_test_project_structure(temp_dir.path());
585
1
        let file_path = create_test_ruchy_file(&project_dir, "export_test.ruchy", "let x = 1");
586
1
        let export_dir = temp_dir.path().join("exports");
587
        
588
1
        let _result = enforce_quality_gates(
589
1
            &file_path,
590
1
            None,
591
1
            "standard",
592
            false,
593
1
            "console",
594
1
            Some(&export_dir),
595
            false,
596
            false,
597
        );
598
        
599
        // Should create export directory
600
1
        assert!(export_dir.exists(), 
"Export directory should be created"0
);
601
1
    }
602
603
    // Test 8: Error Handling
604
    #[test]
605
1
    fn test_invalid_ruchy_syntax() {
606
1
        let temp_dir = TempDir::new().unwrap();
607
1
        let project_dir = create_test_project_structure(temp_dir.path());
608
        
609
        // Create file with invalid syntax
610
1
        let bad_file = create_test_ruchy_file(&project_dir, "bad_syntax.ruchy", "let = = invalid syntax here");
611
        
612
1
        let result = enforce_quality_gates(
613
1
            &bad_file,
614
1
            None,
615
1
            "standard",
616
            false,
617
1
            "console",
618
1
            None,
619
            false,
620
            false,
621
        );
622
        
623
        // Should handle parsing errors gracefully
624
1
        assert!(result.is_err(), 
"Should fail gracefully on invalid syntax"0
);
625
1
    }
626
627
    // Test 9: Verbose Output Mode
628
    #[test]
629
1
    fn test_verbose_mode_flag() {
630
1
        let temp_dir = TempDir::new().unwrap();
631
1
        let project_dir = create_test_project_structure(temp_dir.path());
632
1
        let file_path = create_test_ruchy_file(&project_dir, "verbose_test.ruchy", "let x = 1");
633
        
634
        // Test both verbose modes (this mainly tests that verbose flag is accepted)
635
3
        for 
verbose2
in [true, false] {
636
2
            let result = enforce_quality_gates(
637
2
                &file_path,
638
2
                None,
639
2
                "standard",
640
                false,
641
2
                "console",
642
2
                None,
643
                false,
644
2
                verbose,
645
            );
646
            
647
            // Should accept verbose flag without crashing
648
2
            assert!(result.is_ok() || 
result0
.
is_err0
(),
"Should handle verbose flag"0
);
649
        }
650
1
    }
651
652
    // Test 10: Fail Fast Mode  
653
    #[test]
654
1
    fn test_fail_fast_mode() {
655
1
        let temp_dir = TempDir::new().unwrap();
656
1
        let project_dir = create_test_project_structure(temp_dir.path());
657
        
658
        // Create multiple files
659
1
        create_test_ruchy_file(&project_dir, "fail1.ruchy", "let a = 1");
660
1
        create_test_ruchy_file(&project_dir, "fail2.ruchy", "let b = 2");
661
        
662
3
        for 
fail_fast2
in [true, false] {
663
2
            let result = enforce_quality_gates(
664
2
                &project_dir,
665
2
                None,
666
2
                "standard",
667
2
                fail_fast,
668
2
                "console",
669
2
                None,
670
                false,
671
                false,
672
            );
673
            
674
            // Should handle fail_fast flag without crashing
675
2
            assert!(result.is_ok() || 
result0
.
is_err0
(),
"Should handle fail_fast flag"0
);
676
        }
677
1
    }
678
}