## Coverage Report

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