#![allow(dead_code)]
use forjar::core::plugin_dispatch;
use forjar::core::plugin_hot_reload::{PluginCache, ReloadCheck};
use forjar::core::promotion;
use forjar::core::promotion_events::{self, PromotionParams};
use forjar::core::types::environment::*;
use forjar::core::types::*;
use std::collections::HashMap;
fn make_event(et: EventType) -> InfraEvent {
InfraEvent {
event_type: et,
timestamp: "T".into(),
machine: None,
payload: HashMap::new(),
}
}
fn make_action() -> RulebookAction {
RulebookAction {
apply: Some(ApplyAction {
file: "f.yaml".into(),
subset: vec![],
tags: vec![],
machine: None,
}),
destroy: None,
script: None,
notify: None,
}
}
fn make_rb(name: &str, et: EventType, cooldown: u64) -> Rulebook {
Rulebook {
name: name.into(),
description: None,
events: vec![EventPattern {
event_type: et,
match_fields: HashMap::new(),
}],
conditions: vec![],
actions: vec![make_action()],
cooldown_secs: cooldown,
max_retries: 3,
enabled: true,
}
}
fn valid_config_yaml() -> &'static str {
"version: \"1.0\"\nname: test\nmachines:\n m1:\n hostname: m1\n addr: 127.0.0.1\nresources:\n pkg:\n type: package\n machine: m1\n provider: apt\n packages: [curl]\n"
}
fn setup_plugin(dir: &std::path::Path, name: &str, wasm: &[u8]) {
let pd = dir.join(name);
std::fs::create_dir_all(&pd).unwrap();
let hash = blake3::hash(wasm).to_hex().to_string();
std::fs::write(pd.join("plugin.wasm"), wasm).unwrap();
std::fs::write(pd.join("plugin.yaml"),
format!("name: {name}\nversion: \"0.1.0\"\nabi_version: 1\nwasm: plugin.wasm\nblake3: \"{hash}\"\n")
).unwrap();
}
#[test]
fn f_3400_7_hot_reload_detects_changes() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("m.wasm");
std::fs::write(&path, b"v1").unwrap();
let mut cache = PluginCache::new();
cache.insert(
"m",
PluginManifest {
name: "m".into(),
version: "0.1".into(),
description: None,
abi_version: 1,
wasm: "m.wasm".into(),
blake3: String::new(),
permissions: PluginPermissions::default(),
schema: None,
},
path.clone(),
);
assert_eq!(cache.needs_reload("m"), ReloadCheck::UpToDate);
std::fs::write(&path, b"v2").unwrap();
assert!(cache.needs_reload("m").should_reload());
}
#[test]
fn f_3400_8_no_external_wasm_runtime() {
assert_eq!(
plugin_dispatch::parse_plugin_type("plugin:foo"),
Some("foo")
);
assert!(plugin_dispatch::is_plugin_type("plugin:bar"));
assert!(!plugin_dispatch::is_plugin_type("package"));
let dir = tempfile::tempdir().unwrap();
assert!(plugin_dispatch::available_plugin_types(dir.path()).is_empty());
}
#[test]
fn f_3500_1_environment_state_isolation() {
let base = std::path::Path::new("/state");
assert_ne!(env_state_dir(base, "dev"), env_state_dir(base, "prod"));
assert_eq!(env_state_dir(base, "dev"), base.join("dev"));
}
#[test]
fn f_3500_2_quality_gates_block_promotion() {
let dir = tempfile::tempdir().unwrap();
let cfg = dir.path().join("forjar.yaml");
std::fs::write(&cfg, valid_config_yaml()).unwrap();
let p = PromotionConfig {
from: "dev".into(),
auto_approve: false,
rollout: None,
gates: vec![PromotionGate {
script: Some("false".into()),
..Default::default()
}],
};
let r = promotion::evaluate_gates(&cfg, "staging", &p);
assert!(!r.all_passed && r.failed_count() == 1);
}
#[test]
fn f_3500_3_progressive_rollout_respects_canary() {
let r = RolloutConfig {
strategy: "canary".into(),
canary_count: 1,
health_check: Some("curl -sf http://localhost/health".into()),
health_timeout: Some("30s".into()),
percentage_steps: vec![],
};
assert_eq!(r.strategy, "canary");
assert!(r.health_check.is_some());
}
#[test]
fn f_3500_4_auto_rollback_on_health_failure() {
let dir = tempfile::tempdir().unwrap();
let sd = dir.path().join("state");
std::fs::create_dir_all(&sd).unwrap();
promotion_events::log_rollback(&sd, "prod", 0, "health fail").unwrap();
let c = std::fs::read_to_string(sd.join("prod/events.jsonl")).unwrap();
assert!(c.contains("rollback_triggered") && c.contains("health fail"));
}
#[test]
fn f_3500_5_environment_diff_accuracy() {
let env = Environment::default();
let d = diff_environments(
"a",
&env,
"b",
&env,
&HashMap::new(),
&indexmap::IndexMap::new(),
);
assert!(d.is_identical() && d.total_diffs() == 0);
}
#[test]
fn f_3500_6_promotion_history_append_only() {
let dir = tempfile::tempdir().unwrap();
let sd = dir.path().join("state");
std::fs::create_dir_all(&sd).unwrap();
for i in 0..3 {
promotion_events::log_promotion(&PromotionParams {
state_dir: &sd,
target_env: "stg",
source: "dev",
target: "stg",
gates_passed: i,
gates_total: 3,
rollout_strategy: None,
})
.unwrap();
}
assert_eq!(
std::fs::read_to_string(sd.join("stg/events.jsonl"))
.unwrap()
.lines()
.count(),
3
);
}
#[test]
fn f_3500_7_no_external_cicd_dependency() {
let dir = tempfile::tempdir().unwrap();
let cfg = dir.path().join("forjar.yaml");
std::fs::write(&cfg, valid_config_yaml()).unwrap();
let p = PromotionConfig {
from: "dev".into(),
auto_approve: false,
rollout: None,
gates: vec![PromotionGate {
validate: Some(ValidateGateOptions {
deep: false,
exhaustive: false,
}),
..Default::default()
}],
};
let r = promotion::evaluate_gates(&cfg, "staging", &p);
assert!(r.all_passed);
assert_eq!(r.gates[0].gate_type, "validate");
}
#[test]
fn f_3500_8_config_dry_single_yaml() {
let mut base = HashMap::new();
base.insert(
"log_level".into(),
serde_yaml_ng::Value::String("debug".into()),
);
base.insert("replicas".into(), serde_yaml_ng::Value::String("1".into()));
let mut overrides = HashMap::new();
overrides.insert("replicas".into(), serde_yaml_ng::Value::String("3".into()));
let env = Environment {
params: overrides,
..Default::default()
};
let resolved = resolve_env_params(&base, &env);
assert_eq!(resolved.get("log_level").unwrap(), "debug");
assert_eq!(resolved.get("replicas").unwrap(), "3");
}