#![allow(unused_imports)]
use super::apply::*;
use super::check::*;
use super::diff_cmd::*;
use super::drift::*;
use super::helpers::*;
use super::helpers_state::*;
use super::helpers_time::*;
use super::test_fixtures::*;
use crate::core::types::ProvenanceEvent;
use crate::core::{codegen, executor, migrate, parser, planner, resolver, secrets, state, types};
use crate::transport;
use crate::tripwire::{anomaly, drift, eventlog, tracer};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_auto_commit_in_git_repo() {
let dir = tempfile::tempdir().unwrap();
let config = dir.path().join("forjar.yaml");
let state = dir.path().join("state");
std::fs::create_dir_all(&state).unwrap();
std::process::Command::new("git")
.args(["init"])
.current_dir(dir.path())
.output()
.unwrap();
std::process::Command::new("git")
.args(["config", "user.email", "test@test.com"])
.current_dir(dir.path())
.output()
.unwrap();
std::process::Command::new("git")
.args(["config", "user.name", "Test"])
.current_dir(dir.path())
.output()
.unwrap();
std::fs::write(dir.path().join(".gitkeep"), "").unwrap();
std::process::Command::new("git")
.args(["add", ".gitkeep"])
.current_dir(dir.path())
.output()
.unwrap();
std::process::Command::new("git")
.args(["commit", "-m", "init"])
.current_dir(dir.path())
.output()
.unwrap();
let target = dir.path().join("auto-commit.txt");
std::fs::write(
&config,
format!(
r#"
version: "1.0"
name: autocommit-test
machines:
local:
hostname: localhost
addr: 127.0.0.1
resources:
f:
type: file
machine: local
path: {}
content: "auto commit test"
"#,
target.display()
),
)
.unwrap();
cmd_apply(
&config,
&state,
None,
None,
None,
None, false,
false,
false,
&[],
true,
None, false,
false,
None, None, false, false, None, false, false, 0, true, false,
None,
false,
None,
None,
None, false, None, false, None, false, None, )
.unwrap();
assert!(target.exists());
let output = std::process::Command::new("git")
.args(["log", "--oneline", "-1"])
.current_dir(dir.path())
.output()
.unwrap();
let log = String::from_utf8_lossy(&output.stdout);
assert!(log.contains("forjar:"));
}
#[test]
fn test_drift_alert_cmd() {
let dir = tempfile::tempdir().unwrap();
let state = dir.path().join("state");
let test_file = dir.path().join("drift-alert.txt");
std::fs::write(&test_file, "current").unwrap();
let alert_marker = dir.path().join("alert-fired");
let mut resources = indexmap::IndexMap::new();
let mut details = std::collections::HashMap::new();
details.insert(
"path".to_string(),
serde_yaml_ng::Value::String(test_file.to_str().unwrap().to_string()),
);
details.insert(
"content_hash".to_string(),
serde_yaml_ng::Value::String("blake3:wrong_hash".to_string()),
);
resources.insert(
"drifted-file".to_string(),
crate::core::types::ResourceLock {
resource_type: crate::core::types::ResourceType::File,
status: crate::core::types::ResourceStatus::Converged,
applied_at: Some("2026-01-01T00:00:00Z".to_string()),
duration_seconds: Some(0.1),
hash: "blake3:x".to_string(),
details,
},
);
let lock = crate::core::types::StateLock {
schema: "1.0".to_string(),
machine: "alertbox".to_string(),
hostname: "alertbox".to_string(),
generated_at: "2026-01-01T00:00:00Z".to_string(),
generator: "forjar 0.1.0".to_string(),
blake3_version: "1.8".to_string(),
resources,
};
crate::core::state::save_lock(&state, &lock).unwrap();
let alert_cmd = format!("touch {}", alert_marker.display());
cmd_drift(
Path::new("nonexistent.yaml"),
&state,
None,
false,
Some(&alert_cmd),
false,
false, false,
false,
None, )
.unwrap();
assert!(alert_marker.exists());
}
#[test]
fn test_drift_alert_cmd_not_fired_when_no_drift() {
let dir = tempfile::tempdir().unwrap();
let state = dir.path().join("state");
std::fs::create_dir_all(&state).unwrap();
let alert_marker = dir.path().join("should-not-exist");
let alert_cmd = format!("touch {}", alert_marker.display());
cmd_drift(
Path::new("nonexistent.yaml"),
&state,
None,
false,
Some(&alert_cmd),
false,
false, false,
false,
None, )
.unwrap();
assert!(!alert_marker.exists());
}
#[test]
fn test_drift_auto_remediate() {
let dir = tempfile::tempdir().unwrap();
let config = dir.path().join("forjar.yaml");
let state = dir.path().join("state");
std::fs::create_dir_all(&state).unwrap();
let target = dir
.path()
.join("auto-remediate-test.txt")
.to_string_lossy()
.to_string();
std::fs::write(
&config,
format!(
r#"version: "1.0"
name: remediation-test
machines:
local:
hostname: localhost
addr: 127.0.0.1
resources:
test-file:
type: file
machine: local
path: {target}
content: "original content"
mode: "0644"
"#
),
)
.unwrap();
cmd_apply(
&config,
&state,
None,
None,
None,
None, false,
false,
false,
&[],
false,
None, false,
false,
None, None, false, false, None, false, false, 0, true, false,
None,
false,
None,
None,
None, false, None, false, None, false, None, )
.unwrap();
assert!(std::path::Path::new(&target).exists());
std::fs::write(&target, "tampered content").unwrap();
cmd_drift(
&config, &state, None, false, None, true, false, false, false, None, )
.unwrap();
let content = std::fs::read_to_string(&target).unwrap();
assert_eq!(content.trim(), "original content");
let _ = std::fs::remove_file(&target);
}
#[test]
fn test_drift_dry_run_lists_resources() {
let dir = tempfile::tempdir().unwrap();
let state = dir.path().join("state");
let mut resources = indexmap::IndexMap::new();
resources.insert(
"web-config".to_string(),
crate::core::types::ResourceLock {
resource_type: crate::core::types::ResourceType::File,
status: crate::core::types::ResourceStatus::Converged,
applied_at: None,
duration_seconds: None,
hash: "abc123".to_string(),
details: std::collections::HashMap::new(),
},
);
resources.insert(
"db-config".to_string(),
crate::core::types::ResourceLock {
resource_type: crate::core::types::ResourceType::File,
status: crate::core::types::ResourceStatus::Converged,
applied_at: None,
duration_seconds: None,
hash: "def456".to_string(),
details: std::collections::HashMap::new(),
},
);
let lock = crate::core::types::StateLock {
schema: "1.0".to_string(),
machine: "local".to_string(),
hostname: "local".to_string(),
generated_at: "2026-01-01T00:00:00Z".to_string(),
generator: "forjar 0.1.0".to_string(),
blake3_version: "1.8".to_string(),
resources,
};
crate::core::state::save_lock(&state, &lock).unwrap();
cmd_drift(
Path::new("nonexistent.yaml"),
&state,
None,
false,
None,
false,
true, false,
false,
None, )
.unwrap();
}
#[test]
fn test_diff_added_resource() {
let from_dir = tempfile::tempdir().unwrap();
let to_dir = tempfile::tempdir().unwrap();
make_state_dir_with_lock(
from_dir.path(),
"m1",
vec![("pkg", "blake3:aaa", types::ResourceStatus::Converged)],
);
make_state_dir_with_lock(
to_dir.path(),
"m1",
vec![
("pkg", "blake3:aaa", types::ResourceStatus::Converged),
("conf", "blake3:bbb", types::ResourceStatus::Converged),
],
);
cmd_diff(from_dir.path(), to_dir.path(), None, None, false).unwrap();
}
#[test]
fn test_diff_removed_resource() {
let from_dir = tempfile::tempdir().unwrap();
let to_dir = tempfile::tempdir().unwrap();
make_state_dir_with_lock(
from_dir.path(),
"m1",
vec![
("pkg", "blake3:aaa", types::ResourceStatus::Converged),
("conf", "blake3:bbb", types::ResourceStatus::Converged),
],
);
make_state_dir_with_lock(
to_dir.path(),
"m1",
vec![("pkg", "blake3:aaa", types::ResourceStatus::Converged)],
);
cmd_diff(from_dir.path(), to_dir.path(), None, None, false).unwrap();
}
#[test]
fn test_diff_changed_hash() {
let from_dir = tempfile::tempdir().unwrap();
let to_dir = tempfile::tempdir().unwrap();
make_state_dir_with_lock(
from_dir.path(),
"m1",
vec![("pkg", "blake3:aaa", types::ResourceStatus::Converged)],
);
make_state_dir_with_lock(
to_dir.path(),
"m1",
vec![("pkg", "blake3:bbb", types::ResourceStatus::Converged)],
);
cmd_diff(from_dir.path(), to_dir.path(), None, None, false).unwrap();
}
#[test]
fn test_diff_no_changes() {
let from_dir = tempfile::tempdir().unwrap();
let to_dir = tempfile::tempdir().unwrap();
make_state_dir_with_lock(
from_dir.path(),
"m1",
vec![("pkg", "blake3:aaa", types::ResourceStatus::Converged)],
);
make_state_dir_with_lock(
to_dir.path(),
"m1",
vec![("pkg", "blake3:aaa", types::ResourceStatus::Converged)],
);
cmd_diff(from_dir.path(), to_dir.path(), None, None, false).unwrap();
}
#[test]
fn test_diff_json_output() {
let from_dir = tempfile::tempdir().unwrap();
let to_dir = tempfile::tempdir().unwrap();
make_state_dir_with_lock(
from_dir.path(),
"m1",
vec![("pkg", "blake3:aaa", types::ResourceStatus::Converged)],
);
make_state_dir_with_lock(
to_dir.path(),
"m1",
vec![
("pkg", "blake3:bbb", types::ResourceStatus::Converged),
("svc", "blake3:ccc", types::ResourceStatus::Converged),
],
);
cmd_diff(from_dir.path(), to_dir.path(), None, None, true).unwrap();
}
}