use super::handlers::*;
use super::types::*;
use crate::mcp::handlers::DriftHandler;
use pforge_runtime::Handler;
fn project(dir: &std::path::Path) -> std::path::PathBuf {
let cfg = dir.join("forjar.yaml");
std::fs::write(
&cfg,
format!(
r#"
version: "1.0"
name: parity
machines:
local:
hostname: localhost
addr: localhost
resources:
real:
type: file
machine: local
path: {}
content: hi
action:
type: task
machine: local
phony: true
command: "echo run"
"#,
dir.join("f.txt").display()
),
)
.unwrap();
cfg
}
fn converged_lock(dir: &std::path::Path, cfg: &std::path::Path) {
let config = crate::core::parser::parse_and_validate(cfg).unwrap();
let hash = crate::core::planner::hash_desired_state(&config.resources["real"]);
let md = dir.join("state").join("local");
std::fs::create_dir_all(&md).unwrap();
std::fs::write(
md.join("state.lock.yaml"),
format!(
"schema: \"1.0\"\nmachine: local\nhostname: localhost\ngenerated_at: now\n\
generator: test\nblake3_version: \"1\"\nresources:\n real:\n type: file\n\
\x20 status: converged\n hash: \"{hash}\"\n"
),
)
.unwrap();
std::fs::write(dir.join("f.txt"), "hi").unwrap();
}
#[tokio::test]
async fn plan_reports_only_real_changes_not_every_resource() {
let d = tempfile::tempdir().unwrap();
let cfg = project(d.path());
converged_lock(d.path(), &cfg);
let out = PlanHandler
.handle(PlanInput {
path: cfg.display().to_string(),
state_dir: Some(d.path().join("state").display().to_string()),
resource: None,
tag: None,
})
.await
.expect("plan runs");
assert!(
!out.changes.iter().any(|c| c.resource_id == "real"),
"a converged resource must not be reported as a change: {:?}",
out.changes
);
assert!(
!out.changes.iter().any(|c| c.resource_id == "action"),
"a phony resource is goal-only and must not appear in a bulk plan: {:?}",
out.changes
);
}
#[tokio::test]
async fn plan_still_reports_a_genuine_change() {
let d = tempfile::tempdir().unwrap();
let cfg = project(d.path());
let out = PlanHandler
.handle(PlanInput {
path: cfg.display().to_string(),
state_dir: Some(d.path().join("state").display().to_string()),
resource: None,
tag: None,
})
.await
.expect("plan runs");
assert!(
out.changes.iter().any(|c| c.resource_id == "real"),
"nothing has been applied, so `real` must be planned: {:?}",
out.changes
);
}
#[tokio::test]
async fn status_finds_machines_whose_state_is_a_directory() {
let d = tempfile::tempdir().unwrap();
let cfg = project(d.path());
converged_lock(d.path(), &cfg);
let out = StatusHandler
.handle(StatusInput {
path: None,
state_dir: Some(d.path().join("state").display().to_string()),
machine: None,
})
.await
.expect("status runs");
assert_eq!(
out.machines.len(),
1,
"expected the `local` machine: {:?}",
out.machines
);
assert_eq!(out.machines[0].name, "local");
assert_eq!(out.machines[0].resource_count, 1);
}
#[tokio::test]
async fn status_ignores_a_directory_with_no_lock() {
let d = tempfile::tempdir().unwrap();
std::fs::create_dir_all(d.path().join("state").join("not-a-machine")).unwrap();
let out = StatusHandler
.handle(StatusInput {
path: None,
state_dir: Some(d.path().join("state").display().to_string()),
machine: None,
})
.await
.expect("status runs");
assert!(out.machines.is_empty(), "{:?}", out.machines);
}
#[tokio::test]
async fn plan_without_state_dir_finds_state_beside_the_config() {
let d = tempfile::tempdir().unwrap();
let cfg = project(d.path());
converged_lock(d.path(), &cfg);
let out = PlanHandler
.handle(PlanInput {
path: cfg.display().to_string(), state_dir: None, resource: None,
tag: None,
})
.await
.expect("plan runs");
assert!(
!out.changes.iter().any(|c| c.resource_id == "real"),
"a converged project addressed by absolute path must not report CREATE \
just because the server's cwd is elsewhere (GH-208): {:?}",
out.changes
);
}
#[tokio::test]
async fn drift_without_state_dir_sees_real_drift() {
let d = tempfile::tempdir().unwrap();
let cfg = project(d.path());
let target = d.path().join("f.txt");
std::fs::write(&target, "hi").unwrap();
let content_hash = crate::tripwire::hasher::hash_file(&target).unwrap();
let md = d.path().join("state").join("local");
std::fs::create_dir_all(&md).unwrap();
std::fs::write(
md.join("state.lock.yaml"),
format!(
"schema: \"1.0\"\nmachine: local\nhostname: localhost\ngenerated_at: now\n\
generator: test\nblake3_version: \"1\"\nresources:\n real:\n type: file\n\
\x20 status: converged\n hash: \"h\"\n details:\n path: \"{}\"\n\
\x20 content_hash: \"{}\"\n",
target.display(),
content_hash
),
)
.unwrap();
std::fs::write(&target, "TAMPERED").unwrap();
let out = DriftHandler
.handle(DriftInput {
path: cfg.display().to_string(),
state_dir: None,
machine: None,
})
.await
.expect("drift runs");
assert!(
out.drifted,
"drift is the tripwire tool: reporting a tampered machine as clean \
because the server's cwd is elsewhere is the worst outcome it has \
(GH-208). findings={:?} unchecked={:?}",
out.findings, out.unchecked
);
}
#[tokio::test]
async fn lint_reports_the_same_findings_on_both_surfaces() {
let d = tempfile::tempdir().unwrap();
let cfg = d.path().join("forjar.yaml");
let fake_key = format!("sk_{}_{}", "live", "A".repeat(24));
std::fs::write(
&cfg,
format!(
r#"version: "1.0"
name: parity
machines:
local:
hostname: localhost
addr: localhost
resources:
leaky:
type: file
machine: local
path: /etc/leaky.conf
content: "api_key={fake_key}"
"#
),
)
.unwrap();
let config = crate::core::parser::parse_and_validate(&cfg).unwrap();
let text = std::fs::read_to_string(&cfg).unwrap();
let cli_lines = crate::core::quality_gate::evaluate(
&config,
Some(&text),
&crate::core::quality_gate::GateThresholds::default(),
)
.render();
let out = LintHandler
.handle(LintInput {
path: cfg.display().to_string(),
max_cyclomatic: None,
})
.await
.expect("lint runs");
assert!(!cli_lines.is_empty(), "the fixture must produce a finding");
for line in &cli_lines {
assert!(
out.warnings.contains(line),
"the MCP verb did not report a line the CLI reports: {line:?}\nmcp={:?}",
out.warnings
);
}
assert!(!out.gate_passed, "a plaintext API key must fail the gate");
assert_eq!(out.error_count, 1, "findings={:?}", out.findings);
}