bashrs 6.66.0

Rust-to-Shell transpiler for deterministic bootstrap scripts
fn test_convert_add_to_copy_if_local_malformed_no_args() {
    let line = "ADD";
    let result = convert_add_to_copy_if_local(line);
    assert_eq!(
        result, line,
        "Malformed ADD (no arguments) should be unchanged"
    );
}

#[test]
fn test_convert_add_to_copy_if_local_with_extra_spaces() {
    let line = "ADD    local_file.txt    /app/";
    let result = convert_add_to_copy_if_local(line);
    assert_eq!(
        result, "COPY    local_file.txt    /app/",
        "Should convert ADD to COPY while preserving spacing"
    );
}

#[test]
fn test_convert_add_to_copy_if_local_non_docker_line() {
    let line = "# This is a comment with ADD in it";
    let result = convert_add_to_copy_if_local(line);
    // Should not convert comment lines
    assert_eq!(result, line, "Comment lines should not be processed");
}

// FUNCTION 2: add_no_install_recommends()

#[test]
fn test_add_no_install_recommends_happy_path_with_y_flag() {
    let line = "RUN apt-get install -y curl";
    let result = add_no_install_recommends(line);
    assert_eq!(
        result, "RUN apt-get install -y --no-install-recommends curl",
        "Should add --no-install-recommends after -y flag"
    );
}

#[test]
fn test_add_no_install_recommends_without_y_flag() {
    let line = "RUN apt-get install python3";
    let result = add_no_install_recommends(line);
    assert_eq!(
        result, "RUN apt-get install --no-install-recommends python3",
        "Should add --no-install-recommends after install"
    );
}

#[test]
fn test_add_no_install_recommends_already_present() {
    let line = "RUN apt-get install -y --no-install-recommends git";
    let result = add_no_install_recommends(line);
    assert_eq!(result, line, "Should not add flag if already present");
}

#[test]
fn test_add_no_install_recommends_multiple_packages() {
    let line = "RUN apt-get install -y curl wget git";
    let result = add_no_install_recommends(line);
    assert_eq!(
        result, "RUN apt-get install -y --no-install-recommends curl wget git",
        "Should work with multiple packages"
    );
}

#[test]
fn test_add_no_install_recommends_multiple_apt_get_commands() {
    let line = "RUN apt-get update && apt-get install -y curl && apt-get install -y git";
    let result = add_no_install_recommends(line);
    assert!(
        result.contains("--no-install-recommends"),
        "Should add flag to apt-get install commands"
    );
    // Both install commands should get the flag
    let flag_count = result.matches("--no-install-recommends").count();
    assert_eq!(
        flag_count, 2,
        "Should add flag to both apt-get install commands"
    );
}

#[test]
fn test_add_no_install_recommends_apt_install_variant() {
    let line = "RUN apt install -y vim";
    let result = add_no_install_recommends(line);
    // Note: Current implementation only handles "apt-get install", not "apt install"
    // This test documents current behavior
    assert_eq!(result, line, "apt install (not apt-get) not yet supported");
}

#[test]
fn test_add_no_install_recommends_empty_line() {
    let line = "";
    let result = add_no_install_recommends(line);
    assert_eq!(result, line, "Empty line should be unchanged");
}

#[test]
fn test_add_no_install_recommends_no_apt_get() {
    let line = "RUN echo hello";
    let result = add_no_install_recommends(line);
    assert_eq!(result, line, "Non-apt-get commands should be unchanged");
}

#[test]
fn test_add_no_install_recommends_apt_get_update_only() {
    let line = "RUN apt-get update";
    let result = add_no_install_recommends(line);
    assert_eq!(
        result, line,
        "apt-get update (without install) should be unchanged"
    );
}

#[test]
fn test_add_no_install_recommends_with_continuation() {
    let line = "RUN apt-get install -y \\\n    curl \\\n    wget";
    let result = add_no_install_recommends(line);
    assert!(
        result.contains("--no-install-recommends"),
        "Should handle multi-line continuations"
    );
}

#[test]
fn test_add_no_install_recommends_comment_line() {
    let line = "# RUN apt-get install -y curl";
    let result = add_no_install_recommends(line);
    // Should not process comments
    assert_eq!(result, line, "Comment lines should not be processed");
}

#[test]
fn test_add_no_install_recommends_install_at_end() {
    let line = "RUN apt-get install";
    let result = add_no_install_recommends(line);
    assert_eq!(
        result, "RUN apt-get install --no-install-recommends ",
        "Should add flag even if no packages listed"
    );
}

#[test]
fn test_add_no_install_recommends_preserves_other_flags() {
    let line = "RUN apt-get install -y --fix-missing curl";
    let result = add_no_install_recommends(line);
    assert!(
        result.contains("--fix-missing"),
        "Should preserve other flags"
    );
    assert!(
        result.contains("--no-install-recommends"),
        "Should add --no-install-recommends"
    );
}

// FUNCTION 3: add_package_manager_cleanup()

#[test]
fn test_add_package_manager_cleanup_apt_get_install() {
    let line = "RUN apt-get update && apt-get install -y curl";
    let result = add_package_manager_cleanup(line);
    assert_eq!(
        result, "RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*",
        "Should add apt cleanup after install"
    );
}

#[test]
fn test_add_package_manager_cleanup_apt_install() {
    let line = "RUN apt install -y python3";
    let result = add_package_manager_cleanup(line);
    assert_eq!(
        result, "RUN apt install -y python3 && rm -rf /var/lib/apt/lists/*",
        "Should add apt cleanup for 'apt install' variant"
    );
}

#[test]
fn test_add_package_manager_cleanup_apk_add() {
    let line = "RUN apk add curl";
    let result = add_package_manager_cleanup(line);
    assert_eq!(
        result, "RUN apk add curl && rm -rf /var/cache/apk/*",
        "Should add apk cleanup for Alpine"
    );
}

#[test]
fn test_add_package_manager_cleanup_already_present_apt() {
    let line = "RUN apt-get install -y git && rm -rf /var/lib/apt/lists/*";
    let result = add_package_manager_cleanup(line);
    assert_eq!(result, line, "Should not add cleanup if already present");
}

#[test]
fn test_add_package_manager_cleanup_already_present_apk() {
    let line = "RUN apk add vim && rm -rf /var/cache/apk/*";
    let result = add_package_manager_cleanup(line);
    assert_eq!(
        result, line,
        "Should not add cleanup if already present (apk)"
    );
}

#[test]
fn test_add_package_manager_cleanup_no_package_manager() {
    let line = "RUN echo hello";
    let result = add_package_manager_cleanup(line);
    assert_eq!(
        result, line,
        "Non-package-manager commands should be unchanged"
    );
}

#[test]
fn test_add_package_manager_cleanup_apt_get_update_only() {
    let line = "RUN apt-get update";
    let result = add_package_manager_cleanup(line);
    // update doesn't install packages, so no cleanup needed
    assert_eq!(result, line, "apt-get update alone should be unchanged");
}

#[test]
fn test_add_package_manager_cleanup_empty_line() {
    let line = "";
    let result = add_package_manager_cleanup(line);
    assert_eq!(result, line, "Empty line should be unchanged");
}

#[test]
fn test_add_package_manager_cleanup_comment_line() {
    let line = "# RUN apt-get install curl";
    let result = add_package_manager_cleanup(line);
    assert_eq!(result, line, "Comment lines should not be processed");
}

#[test]
fn test_add_package_manager_cleanup_with_trailing_whitespace() {
    let line = "RUN apt-get install -y wget   ";
    let result = add_package_manager_cleanup(line);
    assert_eq!(
        result, "RUN apt-get install -y wget && rm -rf /var/lib/apt/lists/*",
        "Should trim trailing whitespace before adding cleanup"
    );
}

#[test]
fn test_add_package_manager_cleanup_multiple_commands() {
    let line = "RUN apt-get update && apt-get install -y curl && echo done";
    let result = add_package_manager_cleanup(line);
    assert!(
        result.contains("&& rm -rf /var/lib/apt/lists/*"),
        "Should add cleanup even with multiple commands"
    );
}

#[test]
fn test_add_package_manager_cleanup_apk_add_multiple_packages() {
    let line = "RUN apk add --no-cache curl wget git";
    let result = add_package_manager_cleanup(line);
    assert_eq!(
        result, "RUN apk add --no-cache curl wget git && rm -rf /var/cache/apk/*",
        "Should add cleanup for apk with multiple packages"
    );
}

#[test]
fn test_add_package_manager_cleanup_partial_match_no_install() {
    let line = "RUN apt-get clean";
    let result = add_package_manager_cleanup(line);
    assert_eq!(
        result, line,
        "apt-get clean (not install) should be unchanged"
    );
}

// FUNCTION 4: pin_base_image_version()

#[test]
fn test_pin_base_image_version_ubuntu_untagged() {
    let line = "FROM ubuntu";
    let result = pin_base_image_version(line);
    assert_eq!(
        result, "FROM ubuntu:22.04",
        "Untagged ubuntu should be pinned to 22.04 LTS"
    );
}

#[test]
fn test_pin_base_image_version_ubuntu_latest() {
    let line = "FROM ubuntu:latest";
    let result = pin_base_image_version(line);
    assert_eq!(
        result, "FROM ubuntu:22.04",
        "ubuntu:latest should be pinned to 22.04 LTS"
    );
}

#[test]
fn test_pin_base_image_version_ubuntu_already_pinned() {
    let line = "FROM ubuntu:20.04";
    let result = pin_base_image_version(line);
    assert_eq!(result, line, "Already pinned ubuntu should be unchanged");
}

#[test]
fn test_pin_base_image_version_debian() {
    let line = "FROM debian";
    let result = pin_base_image_version(line);
    assert_eq!(
        result, "FROM debian:12-slim",
        "Untagged debian should be pinned to 12-slim"
    );
}

#[test]
fn test_pin_base_image_version_alpine() {
    let line = "FROM alpine:latest";
    let result = pin_base_image_version(line);
    assert_eq!(
        result, "FROM alpine:3.19",
        "alpine:latest should be pinned to 3.19"
    );
}

#[test]
fn test_pin_base_image_version_node() {
    let line = "FROM node";
    let result = pin_base_image_version(line);
    assert_eq!(
        result, "FROM node:20-alpine",
        "Untagged node should be pinned to 20-alpine"
    );
}

#[test]
fn test_pin_base_image_version_python() {
    let line = "FROM python:latest";
    let result = pin_base_image_version(line);
    assert_eq!(
        result, "FROM python:3.11-slim",
        "python:latest should be pinned to 3.11-slim"
    );
}

#[test]
fn test_pin_base_image_version_with_registry_prefix() {
    let line = "FROM docker.io/ubuntu";
    let result = pin_base_image_version(line);
    assert_eq!(
        result, "FROM docker.io/ubuntu:22.04",
        "Should preserve registry prefix (docker.io/)"
    );
}

#[test]
fn test_pin_base_image_version_with_as_alias() {
    let line = "FROM ubuntu AS builder";
    let result = pin_base_image_version(line);
    assert_eq!(
        result, "FROM ubuntu:22.04 AS builder",
        "Should preserve AS alias"
    );
}

#[test]
fn test_pin_base_image_version_unknown_image() {
    let line = "FROM mycompany/custom-image";
    let result = pin_base_image_version(line);
    assert_eq!(result, line, "Unknown images should be unchanged");
}

#[test]
fn test_pin_base_image_version_malformed_no_image() {
    let line = "FROM";
    let result = pin_base_image_version(line);
    assert_eq!(
        result, line,
        "Malformed FROM (no image) should be unchanged"
    );
}

#[test]
fn test_pin_base_image_version_empty_line() {
    let line = "";
    let result = pin_base_image_version(line);
    assert_eq!(result, line, "Empty line should be unchanged");
}

#[test]
fn test_pin_base_image_version_rust() {
    let line = "FROM rust:latest";
    let result = pin_base_image_version(line);
    assert_eq!(
        result, "FROM rust:1.75-alpine",
        "rust:latest should be pinned to 1.75-alpine"
    );
}