forjar 1.4.1

Rust-native Infrastructure as Code — bare-metal first, BLAKE3 state, provenance tracing
Documentation
//! Tests: FJ-1389 unified stack diff.

#[cfg(test)]
mod tests {
    use super::super::stack_diff::*;
    use std::io::Write;

    fn write_yaml(dir: &std::path::Path, name: &str, content: &str) -> std::path::PathBuf {
        let path = dir.join(name);
        let mut f = std::fs::File::create(&path).unwrap();
        f.write_all(content.as_bytes()).unwrap();
        path
    }

    const CONFIG_BASE: &str = r#"
version: "1.0"
name: test
machines:
  web:
    hostname: web-server
    addr: 10.0.0.1
    arch: x86_64
params:
  env: production
  port: "8080"
resources:
  nginx:
    type: file
    path: /etc/nginx/nginx.conf
    content: "worker_processes auto;"
    machine: web
  app-config:
    type: file
    path: /etc/app/config.yaml
    content: "port: 8080"
    machine: web
outputs:
  web_addr:
    value: "{{machines.web.addr}}"
"#;

    const CONFIG_MODIFIED: &str = r#"
version: "1.0"
name: test-v2
machines:
  web:
    hostname: web-server
    addr: 10.0.0.2
    arch: x86_64
  db:
    hostname: db-server
    addr: 10.0.0.3
    arch: x86_64
params:
  env: staging
  port: "9090"
  region: us-west-2
resources:
  nginx:
    type: file
    path: /etc/nginx/nginx.conf
    content: "worker_processes 4;"
    machine: web
  redis:
    type: file
    path: /etc/redis/redis.conf
    content: "port 6379"
    machine: db
outputs:
  web_addr:
    value: "{{machines.web.addr}}"
  db_addr:
    value: "{{machines.db.addr}}"
"#;

    #[test]
    fn test_stack_diff_identifies_resource_changes() {
        let dir = tempfile::tempdir().unwrap();
        let f1 = write_yaml(dir.path(), "base.yaml", CONFIG_BASE);
        let f2 = write_yaml(dir.path(), "modified.yaml", CONFIG_MODIFIED);

        // Should succeed (we verify via no panic, test output)
        cmd_stack_diff(&f1, &f2, false).unwrap();
    }

    #[test]
    fn test_stack_diff_json_output() {
        let dir = tempfile::tempdir().unwrap();
        let f1 = write_yaml(dir.path(), "base.yaml", CONFIG_BASE);
        let f2 = write_yaml(dir.path(), "modified.yaml", CONFIG_MODIFIED);

        cmd_stack_diff(&f1, &f2, true).unwrap();
    }

    #[test]
    fn test_stack_diff_identical_configs() {
        let dir = tempfile::tempdir().unwrap();
        let f1 = write_yaml(dir.path(), "a.yaml", CONFIG_BASE);
        let f2 = write_yaml(dir.path(), "b.yaml", CONFIG_BASE);

        cmd_stack_diff(&f1, &f2, false).unwrap();
    }

    #[test]
    fn test_stack_diff_nonexistent_file() {
        let dir = tempfile::tempdir().unwrap();
        let f1 = write_yaml(dir.path(), "a.yaml", CONFIG_BASE);
        let f2 = dir.path().join("nonexistent.yaml");

        let result = cmd_stack_diff(&f1, &f2, false);
        assert!(result.is_err());
    }

    #[test]
    fn test_stack_diff_detects_machine_changes() {
        let dir = tempfile::tempdir().unwrap();
        let f1 = write_yaml(dir.path(), "base.yaml", CONFIG_BASE);
        let f2 = write_yaml(dir.path(), "modified.yaml", CONFIG_MODIFIED);

        // Modified has: web addr changed, db added
        cmd_stack_diff(&f1, &f2, true).unwrap();
    }

    #[test]
    fn test_stack_diff_detects_param_changes() {
        let dir = tempfile::tempdir().unwrap();
        let f1 = write_yaml(dir.path(), "base.yaml", CONFIG_BASE);
        let f2 = write_yaml(dir.path(), "modified.yaml", CONFIG_MODIFIED);

        // Modified has: env changed, port changed, region added
        cmd_stack_diff(&f1, &f2, false).unwrap();
    }

    #[test]
    fn test_stack_diff_resource_field_changes() {
        let dir = tempfile::tempdir().unwrap();
        let base = r#"
version: "1.0"
name: test
machines:
  m:
    hostname: m
    addr: 127.0.0.1
resources:
  cfg:
    type: file
    machine: m
    path: /etc/app.conf
    content: "port=8080"
    mode: "0644"
    owner: "root"
    tags: [config]
"#;
        let modified = r#"
version: "1.0"
name: test
machines:
  m:
    hostname: m
    addr: 127.0.0.1
resources:
  cfg:
    type: file
    machine: m
    path: /etc/app-new.conf
    content: "port=9090"
    mode: "0600"
    owner: "app"
    tags: [config, production]
    version: "2.0"
"#;
        let f1 = write_yaml(dir.path(), "base.yaml", base);
        let f2 = write_yaml(dir.path(), "mod.yaml", modified);
        cmd_stack_diff(&f1, &f2, false).unwrap();
    }

    #[test]
    fn test_stack_diff_output_changes() {
        let dir = tempfile::tempdir().unwrap();
        let base = r#"
version: "1.0"
name: test
machines:
  m:
    hostname: m
    addr: 127.0.0.1
resources: {}
outputs:
  url:
    value: "http://old.example.com"
  removed_output:
    value: "gone"
"#;
        let modified = r#"
version: "1.0"
name: test
machines:
  m:
    hostname: m
    addr: 127.0.0.1
resources: {}
outputs:
  url:
    value: "http://new.example.com"
  added_output:
    value: "new"
"#;
        let f1 = write_yaml(dir.path(), "base.yaml", base);
        let f2 = write_yaml(dir.path(), "mod.yaml", modified);
        cmd_stack_diff(&f1, &f2, true).unwrap();
    }

    #[test]
    fn test_stack_diff_param_removed() {
        let dir = tempfile::tempdir().unwrap();
        let base = r#"
version: "1.0"
name: test
machines: {}
resources: {}
params:
  old_key: "old_value"
  shared: "same"
"#;
        let modified = r#"
version: "1.0"
name: test
machines: {}
resources: {}
params:
  shared: "same"
  new_key: "new_value"
"#;
        let f1 = write_yaml(dir.path(), "base.yaml", base);
        let f2 = write_yaml(dir.path(), "mod.yaml", modified);
        cmd_stack_diff(&f1, &f2, false).unwrap();
    }

    #[test]
    fn test_stack_diff_machine_removed() {
        let dir = tempfile::tempdir().unwrap();
        let base = r#"
version: "1.0"
name: test
machines:
  web:
    hostname: web
    addr: 10.0.0.1
  db:
    hostname: db
    addr: 10.0.0.2
resources: {}
"#;
        let modified = r#"
version: "1.0"
name: test
machines:
  web:
    hostname: web
    addr: 10.0.0.1
resources: {}
"#;
        let f1 = write_yaml(dir.path(), "base.yaml", base);
        let f2 = write_yaml(dir.path(), "mod.yaml", modified);
        cmd_stack_diff(&f1, &f2, true).unwrap();
    }

    #[test]
    fn test_stack_diff_machine_arch_change() {
        let dir = tempfile::tempdir().unwrap();
        let base = r#"
version: "1.0"
name: test
machines:
  m:
    hostname: m
    addr: 10.0.0.1
    arch: x86_64
resources: {}
"#;
        let modified = r#"
version: "1.0"
name: test
machines:
  m:
    hostname: m
    addr: 10.0.0.1
    arch: aarch64
resources: {}
"#;
        let f1 = write_yaml(dir.path(), "base.yaml", base);
        let f2 = write_yaml(dir.path(), "mod.yaml", modified);
        cmd_stack_diff(&f1, &f2, false).unwrap();
    }

    #[test]
    fn test_stack_diff_empty_configs() {
        let dir = tempfile::tempdir().unwrap();
        let empty = r#"
version: "1.0"
name: empty
machines: {}
resources: {}
"#;
        let f1 = write_yaml(dir.path(), "a.yaml", empty);
        let f2 = write_yaml(dir.path(), "b.yaml", empty);

        cmd_stack_diff(&f1, &f2, false).unwrap();
        cmd_stack_diff(&f1, &f2, true).unwrap();
    }
}