use std::path::Path;
fn write_machine_lock(state_dir: &Path, machine: &str, yaml: &str) {
let dir = state_dir.join(machine);
std::fs::create_dir_all(&dir).unwrap();
std::fs::write(dir.join("state.lock.yaml"), yaml).unwrap();
}
fn write_config(dir: &Path, yaml: &str) -> std::path::PathBuf {
let file = dir.join("forjar.yaml");
std::fs::write(&file, yaml).unwrap();
file
}
const LOCK_A: &str = r#"schema: "1"
machine: web
hostname: web
generated_at: "2026-03-08T10:00:00Z"
generator: "forjar 1.0.0"
blake3_version: "1.5"
resources:
nginx:
type: file
status: converged
hash: abc123
app:
type: file
status: converged
hash: def456
"#;
const LOCK_B: &str = r#"schema: "1"
machine: web
hostname: web
generated_at: "2026-03-08T11:00:00Z"
generator: "forjar 1.0.0"
blake3_version: "1.5"
resources:
nginx:
type: file
status: converged
hash: abc123
app:
type: file
status: failed
hash: ghi789
redis:
type: package
status: converged
hash: jkl012
"#;
#[test]
fn diff_text_with_changes() {
let from = tempfile::tempdir().unwrap();
let to = tempfile::tempdir().unwrap();
write_machine_lock(from.path(), "web", LOCK_A);
write_machine_lock(to.path(), "web", LOCK_B);
let result = super::diff_cmd::cmd_diff(from.path(), to.path(), None, None, false);
assert!(result.is_ok());
}
#[test]
fn diff_json_with_changes() {
let from = tempfile::tempdir().unwrap();
let to = tempfile::tempdir().unwrap();
write_machine_lock(from.path(), "web", LOCK_A);
write_machine_lock(to.path(), "web", LOCK_B);
let result = super::diff_cmd::cmd_diff(from.path(), to.path(), None, None, true);
assert!(result.is_ok());
}
#[test]
fn diff_no_changes() {
let from = tempfile::tempdir().unwrap();
let to = tempfile::tempdir().unwrap();
write_machine_lock(from.path(), "web", LOCK_A);
write_machine_lock(to.path(), "web", LOCK_A);
let result = super::diff_cmd::cmd_diff(from.path(), to.path(), None, None, false);
assert!(result.is_ok());
}
#[test]
fn diff_machine_filter_match() {
let from = tempfile::tempdir().unwrap();
let to = tempfile::tempdir().unwrap();
write_machine_lock(from.path(), "web", LOCK_A);
write_machine_lock(to.path(), "web", LOCK_B);
let result =
super::diff_cmd::cmd_diff(from.path(), to.path(), Some("web"), None, false);
assert!(result.is_ok());
}
#[test]
fn diff_machine_filter_no_match() {
let from = tempfile::tempdir().unwrap();
let to = tempfile::tempdir().unwrap();
write_machine_lock(from.path(), "web", LOCK_A);
write_machine_lock(to.path(), "web", LOCK_B);
let result =
super::diff_cmd::cmd_diff(from.path(), to.path(), Some("nonexistent"), None, false);
assert!(result.is_err());
assert!(result.unwrap_err().contains("no machines found"));
}
#[test]
fn diff_resource_filter() {
let from = tempfile::tempdir().unwrap();
let to = tempfile::tempdir().unwrap();
write_machine_lock(from.path(), "web", LOCK_A);
write_machine_lock(to.path(), "web", LOCK_B);
let result = super::diff_cmd::cmd_diff(
from.path(),
to.path(),
None,
Some("app"),
false,
);
assert!(result.is_ok());
}
#[test]
fn diff_removed_resource() {
let from = tempfile::tempdir().unwrap();
let to = tempfile::tempdir().unwrap();
write_machine_lock(from.path(), "web", LOCK_B); write_machine_lock(to.path(), "web", LOCK_A); let result = super::diff_cmd::cmd_diff(from.path(), to.path(), None, None, false);
assert!(result.is_ok());
}
#[test]
fn diff_removed_resource_json() {
let from = tempfile::tempdir().unwrap();
let to = tempfile::tempdir().unwrap();
write_machine_lock(from.path(), "web", LOCK_B);
write_machine_lock(to.path(), "web", LOCK_A);
let result = super::diff_cmd::cmd_diff(from.path(), to.path(), None, None, true);
assert!(result.is_ok());
}
#[test]
fn diff_from_has_extra_machine() {
let from = tempfile::tempdir().unwrap();
let to = tempfile::tempdir().unwrap();
write_machine_lock(from.path(), "web", LOCK_A);
write_machine_lock(from.path(), "db", LOCK_A);
write_machine_lock(to.path(), "web", LOCK_A);
let result = super::diff_cmd::cmd_diff(from.path(), to.path(), None, None, false);
assert!(result.is_ok());
}
#[test]
fn env_diff_missing_first() {
let state_dir = tempfile::tempdir().unwrap();
std::fs::create_dir_all(state_dir.path().join("prod")).unwrap();
let result =
super::diff_cmd::cmd_env_diff("staging", "prod", state_dir.path(), false);
assert!(result.is_err());
assert!(result.unwrap_err().contains("not found"));
}
#[test]
fn env_diff_missing_second() {
let state_dir = tempfile::tempdir().unwrap();
std::fs::create_dir_all(state_dir.path().join("staging")).unwrap();
let result =
super::diff_cmd::cmd_env_diff("staging", "prod", state_dir.path(), false);
assert!(result.is_err());
assert!(result.unwrap_err().contains("not found"));
}
#[test]
fn env_diff_identical() {
let state_dir = tempfile::tempdir().unwrap();
write_machine_lock(&state_dir.path().join("staging"), "web", LOCK_A);
write_machine_lock(&state_dir.path().join("prod"), "web", LOCK_A);
let result =
super::diff_cmd::cmd_env_diff("staging", "prod", state_dir.path(), false);
assert!(result.is_ok());
}
#[test]
fn env_diff_with_drifted() {
let state_dir = tempfile::tempdir().unwrap();
write_machine_lock(&state_dir.path().join("staging"), "web", LOCK_A);
write_machine_lock(&state_dir.path().join("prod"), "web", LOCK_B);
let result =
super::diff_cmd::cmd_env_diff("staging", "prod", state_dir.path(), false);
assert!(result.is_ok());
}
#[test]
fn env_diff_json() {
let state_dir = tempfile::tempdir().unwrap();
write_machine_lock(&state_dir.path().join("staging"), "web", LOCK_A);
write_machine_lock(&state_dir.path().join("prod"), "web", LOCK_B);
let result =
super::diff_cmd::cmd_env_diff("staging", "prod", state_dir.path(), true);
assert!(result.is_ok());
}
#[test]
fn env_text_with_config() {
let dir = tempfile::tempdir().unwrap();
let file = write_config(
dir.path(),
r#"
version: "1.0"
name: env-test
machines:
local:
hostname: localhost
addr: 127.0.0.1
resources:
cfg:
type: file
machine: local
path: /tmp/env-test.txt
content: hello
"#,
);
let result = super::diff_cmd::cmd_env(&file, false);
assert!(result.is_ok());
}
#[test]
fn env_json_with_config() {
let dir = tempfile::tempdir().unwrap();
let file = write_config(
dir.path(),
r#"
version: "1.0"
name: env-json
machines:
local:
hostname: localhost
addr: 127.0.0.1
resources: {}
"#,
);
let result = super::diff_cmd::cmd_env(&file, true);
assert!(result.is_ok());
}
#[test]
fn env_text_missing_config() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("nonexistent.yaml");
let result = super::diff_cmd::cmd_env(&file, false);
assert!(result.is_ok());
}
#[test]
fn env_json_missing_config() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("nonexistent.yaml");
let result = super::diff_cmd::cmd_env(&file, true);
assert!(result.is_ok());
}
#[test]
fn env_text_invalid_config() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("forjar.yaml");
std::fs::write(&file, "invalid: yaml: [broken").unwrap();
let result = super::diff_cmd::cmd_env(&file, false);
assert!(result.is_ok());
}