migs 0.1.8

A SQL migration script collection library with compile-time registration
Documentation
//! Integration tests for the migs macro library

use migs::{collect, migs, Migs};

// Register some test migrations with explicit scope and order
migs!(
    path = "tests/migrations/001_init.sql",
    scope = "init",
    order = 1
);
migs!(
    path = "tests/migrations/002_indexes.sql",
    scope = "indexes",
    order = 1
);
migs!(
    sql = "INSERT INTO {{table}} (name) VALUES ('test')",
    scope = "seed",
    order = 1
);
migs!(
    sql = "UPDATE {{table}} SET name = 'updated' WHERE id = {{id}}",
    scope = "update",
    order = 1
);

// Register some test migrations WITHOUT scope (should default to "init")
// and WITHOUT order (should be None)
migs!(path = "tests/migrations/001_init.sql");
migs!(sql = "SELECT 1 FROM {{table}}");

#[test]
fn test_collect_returns_all_scripts() {
    let scripts: Vec<&Migs> = collect!();

    // We should have 6 registered scripts (4 with explicit scope + 2 without)
    assert!(!scripts.is_empty(), "Should have registered scripts");
    assert_eq!(scripts.len(), 6, "Should have exactly 6 registered scripts");
}

#[test]
fn test_scripts_have_correct_scopes() {
    let scripts: Vec<&Migs> = collect!();

    let scopes: Vec<&str> = scripts.iter().map(|s| s.scope).collect();

    // Check for explicit scopes
    assert!(scopes.contains(&"init"), "Should have init scope");
    assert!(scopes.contains(&"indexes"), "Should have indexes scope");
    assert!(scopes.contains(&"seed"), "Should have seed scope");
    assert!(scopes.contains(&"update"), "Should have update scope");
}

#[test]
fn test_default_scope_is_init() {
    let scripts: Vec<&Migs> = collect!();

    // Count how many scripts have the "init" scope
    // We have: 1 explicit "init" + 2 without scope (defaults to "init") = 3 total
    let init_scripts: Vec<_> = scripts.iter().filter(|s| s.scope == "init").collect();
    assert_eq!(
        init_scripts.len(),
        3,
        "Should have 3 scripts with 'init' scope (1 explicit + 2 default)"
    );
}

#[test]
fn test_scripts_have_correct_sources() {
    let scripts: Vec<&Migs> = collect!();

    // Find the file-based scripts
    let file_scripts: Vec<_> = scripts
        .iter()
        .filter(|s| s.source.starts_with("tests/migrations/"))
        .collect();
    assert_eq!(file_scripts.len(), 3, "Should have 3 file-based scripts");

    // Find the inline scripts
    let inline_scripts: Vec<_> = scripts.iter().filter(|s| s.source == "<inline>").collect();
    assert_eq!(inline_scripts.len(), 3, "Should have 3 inline scripts");
}

#[test]
fn test_scripts_have_content() {
    let scripts: Vec<&Migs> = collect!();

    for script in &scripts {
        assert!(!script.content.is_empty(), "Script should have content");
    }

    // Check that the init script has CREATE TABLE
    let init_scripts: Vec<_> = scripts.iter().filter(|s| s.scope == "init").collect();
    assert!(
        !init_scripts.is_empty(),
        "Should have at least one init script"
    );

    // At least one of the init scripts should have CREATE TABLE
    let has_create_table = init_scripts
        .iter()
        .any(|s| s.content.contains("CREATE TABLE"));
    assert!(
        has_create_table,
        "At least one init script should contain CREATE TABLE"
    );
}

#[test]
fn test_inline_sql_has_placeholders() {
    let scripts: Vec<&Migs> = collect!();

    // Find the seed script
    let seed_script = scripts
        .iter()
        .find(|s| s.scope == "seed")
        .expect("Should find seed script");

    assert!(
        seed_script.content.contains("{{table}}"),
        "Seed script should have {{table}} placeholder"
    );
}

#[test]
fn test_default_scope_script_has_placeholder() {
    let scripts: Vec<&Migs> = collect!();

    // Find the default scope script (the one without explicit scope but with sql)
    let default_sql_script = scripts
        .iter()
        .find(|s| {
            s.scope == "init" && s.source == "<inline>" && s.content == "SELECT 1 FROM {{table}}"
        })
        .expect("Should find default scope inline script");

    assert_eq!(
        default_sql_script.scope, "init",
        "Default scope should be 'init'"
    );
    assert!(
        default_sql_script.content.contains("{{table}}"),
        "Should have placeholder"
    );
}

#[test]
fn test_scripts_have_orders() {
    let scripts: Vec<&Migs> = collect!();

    // Find scripts with explicit orders
    let ordered_scripts: Vec<_> = scripts.iter().filter(|s| s.order.is_some()).collect();
    assert_eq!(
        ordered_scripts.len(),
        4,
        "Should have 4 scripts with explicit orders"
    );

    // Verify the orders are correct
    let init_script = scripts
        .iter()
        .find(|s| {
            s.scope == "init" && s.source == "tests/migrations/001_init.sql" && s.order == Some(1)
        })
        .expect("Should find init script with order 1");
    assert_eq!(init_script.order, Some(1));

    let indexes_script = scripts
        .iter()
        .find(|s| s.scope == "indexes" && s.order == Some(1))
        .expect("Should find indexes script with order 1");
    assert_eq!(indexes_script.order, Some(1));
}

#[test]
fn test_scripts_without_order_have_none() {
    let scripts: Vec<&Migs> = collect!();

    // Find scripts without explicit order - they should have order: None
    let unordered_scripts: Vec<_> = scripts.iter().filter(|s| s.order.is_none()).collect();
    assert_eq!(
        unordered_scripts.len(),
        2,
        "Should have 2 scripts without explicit orders"
    );
}

#[test]
fn test_same_order_different_scopes_allowed() {
    let scripts: Vec<&Migs> = collect!();

    // Multiple scopes can use the same order number
    let order_1_scripts: Vec<_> = scripts.iter().filter(|s| s.order == Some(1)).collect();
    assert_eq!(
        order_1_scripts.len(),
        4,
        "Should have 4 scripts with order 1 across different scopes"
    );

    // Verify they are in different scopes
    let scopes_with_order_1: std::collections::HashSet<_> =
        order_1_scripts.iter().map(|s| s.scope).collect();
    assert_eq!(
        scopes_with_order_1.len(),
        4,
        "Order 1 should be used in 4 different scopes"
    );
}