bashrs 6.66.0

Rust-to-Shell transpiler for deterministic bootstrap scripts
#![allow(deprecated)]
#![allow(clippy::unwrap_used)] // Tests can use unwrap() for simplicity
#![allow(clippy::expect_used)]
//! Canonical Matrix Test - Comprehensive Smoke Test for All File Types
//!
//! This test verifies the complete capability matrix for bashrs across all supported file types:
//! - script.sh: Purification + Linting
//! - Makefile: Purification + Test Generation
//! - Dockerfile: Purification (6 transformations)
//!
//! **Purpose**: Quick, comprehensive smoke test suitable for pre-commit hooks
//! **Performance Target**: <5 seconds total execution time
//! **Coverage**: All major capabilities with property-based testing
//!
//! Test Naming Convention: test_MATRIX_<file_type>_<capability>

#![allow(non_snake_case)] // Test naming convention uses MATRIX prefix

use assert_cmd::Command;
use std::fs;
use std::io::Write;
use tempfile::{NamedTempFile, TempDir};

// ============================================================================
// Helper Functions
// ============================================================================

/// Create bashrs command (MANDATORY pattern per CLAUDE.md)
#[allow(deprecated)]
fn bashrs_cmd() -> Command {
    assert_cmd::cargo_bin_cmd!("bashrs")
}

/// Create temporary bash script
fn create_temp_bash_script(content: &str) -> NamedTempFile {
    let mut file = NamedTempFile::new().expect("Failed to create temp file");
    file.write_all(content.as_bytes())
        .expect("Failed to write to temp file");
    file
}

/// Create temporary Makefile
fn create_temp_makefile(content: &str) -> NamedTempFile {
    let mut file = NamedTempFile::new().expect("Failed to create temp file");
    file.write_all(content.as_bytes())
        .expect("Failed to write to temp file");
    file
}

/// Create temporary Dockerfile
fn create_temp_dockerfile(content: &str) -> NamedTempFile {
    let mut file = NamedTempFile::new().expect("Failed to create temp file");
    file.write_all(content.as_bytes())
        .expect("Failed to write to temp file");
    file
}

// ============================================================================
// MATRIX TEST 1: Bash Script Purification
// ============================================================================

#[test]
fn test_MATRIX_bash_purification_basics() {
    // Test bash purification removes non-deterministic elements
    let bash_script = r#"#!/bin/bash
x=$RANDOM
pid=$$
echo "Random: $x, PID: $pid"
"#;

    let input_file = create_temp_bash_script(bash_script);
    let output_dir = TempDir::new().expect("Failed to create temp dir");
    let output_file = output_dir.path().join("purified.sh");

    bashrs_cmd()
        .arg("purify")
        .arg(input_file.path())
        .arg("-o")
        .arg(&output_file)
        .assert()
        .success();

    let purified = fs::read_to_string(&output_file).expect("Failed to read purified file");

    // Verify determinism: $RANDOM and $$ should be replaced or handled
    assert!(
        !purified.contains("$RANDOM") || purified.contains("# $RANDOM"),
        "Purification should handle $RANDOM"
    );
}

// ============================================================================
// MATRIX TEST 2: Bash Script Linting
// ============================================================================

#[test]
fn test_MATRIX_bash_linting_basics() {
    // Test bash linting detects common issues
    let bash_script = r#"#!/bin/bash
# Unquoted variable (SC2086)
echo $unquoted_var

# Undefined variable (SC2154)
echo $UNDEFINED_VAR
"#;

    let input_file = create_temp_bash_script(bash_script);

    // Note: lint returns exit code 1 when issues are found (this is expected behavior)
    let output = bashrs_cmd()
        .arg("lint")
        .arg(input_file.path())
        .output()
        .expect("Failed to execute lint command");

    // Verify lint command executed (exit code 0 or 1 both acceptable)
    // Code 0: No issues found, Code 1: Issues found (warnings/errors)
    assert!(
        output.status.code() == Some(0) || output.status.code() == Some(1),
        "Lint should return exit code 0 (clean) or 1 (issues found)"
    );

    let stdout = String::from_utf8_lossy(&output.stdout);

    // Verify linting produces output (detects issues)
    assert!(
        stdout.contains("SC2086") || stdout.contains("SC2154") || !stdout.is_empty(),
        "Lint should detect issues or produce output"
    );
}

// ============================================================================
// MATRIX TEST 3: Makefile Purification
// ============================================================================

#[test]
fn test_MATRIX_makefile_purification_basics() {
    // Test Makefile purification applies transformations
    let makefile = r#"
build:
	gcc -o app main.c
	gcc -o test test.c

clean:
	rm -f app test
"#;

    let input_file = create_temp_makefile(makefile);
    let output_dir = TempDir::new().expect("Failed to create temp dir");
    let output_file = output_dir.path().join("Makefile");

    bashrs_cmd()
        .arg("make")
        .arg("purify")
        .arg(input_file.path())
        .arg("-o")
        .arg(&output_file)
        .assert()
        .success();

    let purified = fs::read_to_string(&output_file).expect("Failed to read purified Makefile");

    // Verify purification succeeded and structure preserved
    assert!(purified.contains("build:"), "Build target preserved");
    assert!(purified.contains("clean:"), "Clean target preserved");
}

// ============================================================================
// MATRIX TEST 4: Makefile Test Generation
// ============================================================================

#[test]
fn test_MATRIX_makefile_test_generation_basics() {
    // Test Makefile test suite generation
    let makefile = r#"
.PHONY: build
build:
	@echo "Building..."

.PHONY: test
test:
	@echo "Testing..."
"#;

    let input_file = create_temp_makefile(makefile);
    let output_dir = TempDir::new().expect("Failed to create temp dir");
    let output_file = output_dir.path().join("Makefile");

    bashrs_cmd()
        .arg("make")
        .arg("purify")
        .arg(input_file.path())
        .arg("--with-tests")
        .arg("-o")
        .arg(&output_file)
        .assert()
        .success();

    // Verify test suite was generated
    let test_file = output_dir.path().join("Makefile.test.sh");
    assert!(
        test_file.exists(),
        "Test suite should be generated with --with-tests flag"
    );

    let test_content = fs::read_to_string(&test_file).expect("Failed to read test suite");
    assert!(
        test_content.contains("#!/bin/sh") || test_content.contains("#!/usr/bin/env sh"),
        "Test suite should be a valid shell script"
    );
}

// ============================================================================
// MATRIX TEST 5: Dockerfile Purification
// ============================================================================

#[test]
fn test_MATRIX_dockerfile_purification_basics() {
    // Test Dockerfile purification applies all 6 transformations
    let dockerfile = r#"FROM ubuntu:latest
RUN apt-get update && apt-get install -y curl
ADD local-file.txt /app/
"#;

    let input_file = create_temp_dockerfile(dockerfile);
    let output_dir = TempDir::new().expect("Failed to create temp dir");
    let output_file = output_dir.path().join("Dockerfile");

    bashrs_cmd()
        .arg("dockerfile")
        .arg("purify")
        .arg(input_file.path())
        .arg("-o")
        .arg(&output_file)
        .assert()
        .success();

    let purified = fs::read_to_string(&output_file).expect("Failed to read purified Dockerfile");

    // Verify DOCKER001: FROM latest transformed
    assert!(
        !purified.contains("ubuntu:latest"),
        "DOCKER001: FROM :latest should be transformed"
    );

    // Verify DOCKER005: --no-install-recommends added
    assert!(
        purified.contains("--no-install-recommends"),
        "DOCKER005: Should add --no-install-recommends"
    );

    // Verify DOCKER003: Package cleanup added
    assert!(
        purified.contains("/var/lib/apt/lists"),
        "DOCKER003: Should add package cleanup"
    );

    // Verify DOCKER006: ADD → COPY for local files
    assert!(
        purified.contains("COPY local-file.txt"),
        "DOCKER006: ADD should be converted to COPY for local files"
    );
}

// ============================================================================
// MATRIX TEST 6: Cross-File Type Determinism (Property Test)
// ============================================================================

#[test]
fn test_MATRIX_property_purification_determinism() {
    // Property: All purification operations are deterministic
    // Running purify twice on same input produces identical output

    let test_cases = vec![
        (
            "bash",
            r#"#!/bin/bash
x=$RANDOM
echo $x
"#,
        ),
        (
            "makefile",
            r#"
build:
	gcc -o app main.c
"#,
        ),
        (
            "dockerfile",
            r#"FROM ubuntu:latest
RUN apt-get install -y curl
"#,
        ),
    ];

    for (file_type, content) in test_cases {
        let temp_dir = TempDir::new().expect("Failed to create temp dir");
        let input_path = temp_dir.path().join(format!("input.{}", file_type));
        fs::write(&input_path, content).expect("Failed to write input file");

        let output1 = temp_dir.path().join("output1");
        let output2 = temp_dir.path().join("output2");

        // First purification
        let cmd = match file_type {
            "bash" => bashrs_cmd()
                .arg("purify")
                .arg(&input_path)
                .arg("-o")
                .arg(&output1)
                .ok(),
            "makefile" => bashrs_cmd()
                .arg("make")
                .arg("purify")
                .arg(&input_path)
                .arg("-o")
                .arg(&output1)
                .ok(),
            "dockerfile" => bashrs_cmd()
                .arg("dockerfile")
                .arg("purify")
                .arg(&input_path)
                .arg("-o")
                .arg(&output1)
                .ok(),
            _ => panic!("Unknown file type"),
        };

        if cmd.is_err() {
            // Skip if command not available (e.g., bash purification not yet implemented)
            continue;
        }

        // Second purification
        match file_type {
            "bash" => {
                bashrs_cmd()
                    .arg("purify")
                    .arg(&input_path)
                    .arg("-o")
                    .arg(&output2)
                    .assert()
                    .success();
            }
            "makefile" => {
                bashrs_cmd()
                    .arg("make")
                    .arg("purify")
                    .arg(&input_path)
                    .arg("-o")
                    .arg(&output2)
                    .assert()
                    .success();
            }
            "dockerfile" => {
                bashrs_cmd()
                    .arg("dockerfile")
                    .arg("purify")
                    .arg(&input_path)
                    .arg("-o")
                    .arg(&output2)
                    .assert()
                    .success();
            }
            _ => {}
        }

        // Verify determinism
        let content1 = fs::read_to_string(&output1).expect("Failed to read output1");
        let content2 = fs::read_to_string(&output2).expect("Failed to read output2");

        assert_eq!(
            content1, content2,
            "{} purification should be deterministic",
            file_type
        );
    }
}

// ============================================================================
// MATRIX TEST 7: Cross-File Type Idempotency (Property Test)
// ============================================================================

#[test]

include!("canonical_matrix_test_matrix_prope.rs");