sqlweld 0.2.0

Create SQL files from templates and partials
Documentation
use tempfile::TempDir;

use super::{build, Error, Options};

const UPDATE_SOME_OBJECTS: &'static str = include_str!("../test_data/update_some_objects.sql.tera");
const GET_SOME_OBJECTS: &'static str = include_str!("../test_data/get_some_objects.sql.tera");
const OTHER_TEMPLATE: &'static str = include_str!("../test_data/other_template.tera");
const PERM_CHECK: &'static str = include_str!("../test_data/perm_check.partial.sql.tera");
const ROOT_PARTIAL: &'static str = include_str!("../test_data/root.partial.sql.tera");
const USES_ROOT_PARTIAL: &'static str = include_str!("../test_data/uses_root.sql.tera");

const EXPECTED_USES_ROOT_PARTIAL: &'static str = include_str!("../test_data/uses_root.sql");

const EXPECTED_UPDATE_SOME_OBJECTS: &'static str =
    include_str!("../test_data/update_some_objects.sql");
const EXPECTED_GET_SOME_OBJECTS: &'static str = include_str!("../test_data/get_some_objects.sql");
const HEADER: &'static str = "-- Autogenerated by sqlweld";

fn strip_header(s: &str) -> &str {
    s.strip_prefix(HEADER).unwrap_or(s).trim_start()
}

fn apply_header(header: &str, base_expected: &str) -> String {
    let expected = strip_header(base_expected);
    if header.is_empty() {
        expected.to_string()
    } else {
        format!("{header}\n\n{expected}")
    }
}

fn create_input() -> TempDir {
    let dir = tempfile::tempdir().unwrap();
    let dirpath = dir.path();

    std::fs::write(
        dirpath.join("update_some_objects.sql.tera"),
        UPDATE_SOME_OBJECTS,
    )
    .unwrap();
    std::fs::write(dirpath.join("get_some_objects.sql.tera"), GET_SOME_OBJECTS).unwrap();
    std::fs::write(dirpath.join("other_template.tera"), OTHER_TEMPLATE).unwrap();
    std::fs::write(dirpath.join("perm_check.partial.sql.tera"), PERM_CHECK).unwrap();

    dir
}

#[test]
fn normal_test() {
    let dir = create_input();
    let path = dir.path().to_owned();

    build(Options {
        input: Some(path.clone()),
        ..Default::default()
    })
    .unwrap();

    assert_eq!(
        std::fs::read_to_string(path.join("update_some_objects.sql")).unwrap(),
        apply_header(HEADER, EXPECTED_UPDATE_SOME_OBJECTS)
    );
    assert_eq!(
        std::fs::read_to_string(path.join("get_some_objects.sql")).unwrap(),
        apply_header(HEADER, EXPECTED_GET_SOME_OBJECTS)
    );

    assert!(std::fs::File::open(path.join("other_template.sql")).is_err());
    assert!(std::fs::File::open(path.join("perm_check.sql")).is_err());
}

#[test]
fn separate_output() {
    let dir = create_input();
    let path = dir.path().to_owned();

    let output = dir.path().join("output");
    std::fs::create_dir(&output).unwrap();

    build(Options {
        input: Some(path.clone()),
        output: Some(output.clone()),
        ..Default::default()
    })
    .unwrap();

    assert_eq!(
        std::fs::read_to_string(output.join("update_some_objects.sql")).unwrap(),
        apply_header(HEADER, EXPECTED_UPDATE_SOME_OBJECTS)
    );
    assert_eq!(
        std::fs::read_to_string(output.join("get_some_objects.sql")).unwrap(),
        apply_header(HEADER, EXPECTED_GET_SOME_OBJECTS)
    );

    assert!(std::fs::File::open(output.join("other_template.sql")).is_err());
    assert!(std::fs::File::open(output.join("perm_check.sql")).is_err());
}

#[test]
fn custom_header() {
    let dir = create_input();
    let path = dir.path().to_owned();

    build(Options {
        input: Some(path.clone()),
        header: Some("custom header".to_string()),
        ..Default::default()
    })
    .unwrap();

    assert_eq!(
        std::fs::read_to_string(path.join("update_some_objects.sql")).unwrap(),
        apply_header("-- custom header", EXPECTED_UPDATE_SOME_OBJECTS)
    );
    assert_eq!(
        std::fs::read_to_string(path.join("get_some_objects.sql")).unwrap(),
        apply_header("-- custom header", EXPECTED_GET_SOME_OBJECTS)
    );

    assert!(std::fs::File::open(path.join("other_template.sql")).is_err());
    assert!(std::fs::File::open(path.join("perm_check.sql")).is_err());
}

#[test]
fn no_header() {
    let dir = create_input();
    let path = dir.path().to_owned();

    build(Options {
        input: Some(path.clone()),
        header: Some("".to_string()),
        ..Default::default()
    })
    .unwrap();

    assert_eq!(
        std::fs::read_to_string(path.join("update_some_objects.sql")).unwrap(),
        strip_header(EXPECTED_UPDATE_SOME_OBJECTS)
    );
    assert_eq!(
        std::fs::read_to_string(path.join("get_some_objects.sql")).unwrap(),
        strip_header(EXPECTED_GET_SOME_OBJECTS)
    );

    assert!(std::fs::File::open(path.join("other_template.sql")).is_err());
    assert!(std::fs::File::open(path.join("perm_check.sql")).is_err());
}

#[test]
fn custom_header_multiline() {
    let dir = create_input();
    let path = dir.path().to_owned();

    build(Options {
        input: Some(path.clone()),
        header: Some("custom header\r\nand another line".to_string()),
        ..Default::default()
    })
    .unwrap();

    assert_eq!(
        std::fs::read_to_string(path.join("update_some_objects.sql")).unwrap(),
        apply_header(
            "-- custom header\n-- and another line",
            EXPECTED_UPDATE_SOME_OBJECTS
        )
    );
    assert_eq!(
        std::fs::read_to_string(path.join("get_some_objects.sql")).unwrap(),
        apply_header(
            "-- custom header\n-- and another line",
            EXPECTED_GET_SOME_OBJECTS
        )
    );

    assert!(std::fs::File::open(path.join("other_template.sql")).is_err());
    assert!(std::fs::File::open(path.join("perm_check.sql")).is_err());
}

#[test]
fn custom_extension() {
    let dir = create_input();
    let path = dir.path().to_owned();

    build(Options {
        input: Some(path.clone()),
        extension: Some("gen.sql".to_string()),
        ..Default::default()
    })
    .unwrap();

    assert_eq!(
        std::fs::read_to_string(path.join("update_some_objects.gen.sql")).unwrap(),
        apply_header(HEADER, EXPECTED_UPDATE_SOME_OBJECTS)
    );
    assert_eq!(
        std::fs::read_to_string(path.join("get_some_objects.gen.sql")).unwrap(),
        apply_header(HEADER, EXPECTED_GET_SOME_OBJECTS)
    );

    assert!(std::fs::File::open(path.join("other_template.sql")).is_err());
    assert!(std::fs::File::open(path.join("perm_check.sql")).is_err());
}

#[test]
fn duplicate_partials() {
    let dir = create_input();
    let path = dir.path().to_owned();

    let dir1 = path.join("dir1");
    std::fs::create_dir(&dir1).unwrap();
    let dir2 = path.join("dir2");
    std::fs::create_dir(&dir2).unwrap();

    std::fs::write(dir1.join("dup.partial.sql.tera"), "abc").unwrap();
    std::fs::write(dir2.join("dup.macros.sql.tera"), "def").unwrap();

    let err = build(Options {
        input: Some(path.clone()),
        ..Default::default()
    })
    .expect_err("should fail");

    println!("{:#?}", err);

    assert!(matches!(err.current_context(), Error::DuplicatePartial));
}

#[test]
fn inheritance() {
    let dir = create_input();
    let path = dir.path().to_owned();

    std::fs::write(path.join("root.partial.sql.tera"), ROOT_PARTIAL).unwrap();
    std::fs::write(path.join("uses_root.sql.tera"), USES_ROOT_PARTIAL).unwrap();

    build(Options {
        input: Some(path.clone()),
        ..Default::default()
    })
    .unwrap();

    assert_eq!(
        std::fs::read_to_string(path.join("uses_root.sql")).unwrap(),
        apply_header(HEADER, EXPECTED_USES_ROOT_PARTIAL)
    );
}

#[test]
fn inherit_from_parent_dir() {
    let dir = create_input();
    let path = dir.path().to_owned();

    std::fs::create_dir_all(path.join("in_subdir")).unwrap();
    std::fs::write(path.join("root.partial.sql.tera"), ROOT_PARTIAL).unwrap();
    std::fs::write(
        path.join("in_subdir").join("uses_root.sql.tera"),
        USES_ROOT_PARTIAL,
    )
    .unwrap();

    build(Options {
        input: Some(path.clone()),
        ..Default::default()
    })
    .unwrap();

    assert_eq!(
        std::fs::read_to_string(path.join("in_subdir").join("uses_root.sql")).unwrap(),
        apply_header(HEADER, EXPECTED_USES_ROOT_PARTIAL)
    );
}

#[test]
fn skip_write_if_not_changed() {
    let dir = create_input();
    let path = dir.path().to_owned();

    std::fs::write(
        path.join("update_some_objects.sql"),
        apply_header(HEADER, EXPECTED_UPDATE_SOME_OBJECTS),
    )
    .unwrap();

    let current_mtime = std::fs::metadata(path.join("update_some_objects.sql"))
        .unwrap()
        .modified()
        .unwrap();

    std::thread::sleep(std::time::Duration::from_millis(1));

    build(Options {
        input: Some(path.clone()),
        ..Default::default()
    })
    .unwrap();

    let new_mtime = std::fs::metadata(path.join("update_some_objects.sql"))
        .unwrap()
        .modified()
        .unwrap();

    println!("current_mtime: {:?}", current_mtime);
    println!("new_mtime: {:?}", new_mtime);

    assert_eq!(
        current_mtime, new_mtime,
        "file should not have been changed"
    );
    assert_eq!(
        std::fs::read_to_string(path.join("update_some_objects.sql")).unwrap(),
        apply_header(HEADER, EXPECTED_UPDATE_SOME_OBJECTS)
    );
    assert_eq!(
        std::fs::read_to_string(path.join("get_some_objects.sql")).unwrap(),
        apply_header(HEADER, EXPECTED_GET_SOME_OBJECTS)
    );

    assert!(std::fs::File::open(path.join("other_template.sql")).is_err());
    assert!(std::fs::File::open(path.join("perm_check.sql")).is_err());
}

#[test]
fn overwrite() {
    let dir = create_input();
    let path = dir.path().to_owned();

    std::fs::write(path.join("update_some_objects.sql"), "some old content").unwrap();

    build(Options {
        input: Some(path.clone()),
        ..Default::default()
    })
    .unwrap();

    assert_eq!(
        std::fs::read_to_string(path.join("update_some_objects.sql")).unwrap(),
        apply_header(HEADER, EXPECTED_UPDATE_SOME_OBJECTS)
    );
    assert_eq!(
        std::fs::read_to_string(path.join("get_some_objects.sql")).unwrap(),
        apply_header(HEADER, EXPECTED_GET_SOME_OBJECTS)
    );

    assert!(std::fs::File::open(path.join("other_template.sql")).is_err());
    assert!(std::fs::File::open(path.join("perm_check.sql")).is_err());
}