use ferroclass::inventory::load;
use ferroclass::inventory::options::{
Options, OutputFormat, OutputOptions, ParameterKeyStyle, StorageOptions, StorageType,
YamlFsStorageOptions,
};
use ferroclass::output::format_output;
use ferroclass::output::reclass::{InventoryOutput, NodeInfoOutput};
use std::path::PathBuf;
const TEST_TIMESTAMP: &str = "Thu Jan 1 00:00:00 2026";
fn normalize_uris(output: &str) -> String {
output.replace(env!("CARGO_MANIFEST_DIR"), "$PROJECT_ROOT")
}
fn e2e_inventory_path() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("inventories")
.join("e2e")
}
fn python_compat_path() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("inventories")
.join("python_compat")
}
fn load_e2e_inventory() -> ferroclass::inventory::Inventory {
let path = e2e_inventory_path();
let storage_options = StorageOptions {
storage_type: StorageType::YamlFs,
yaml_fs_options: YamlFsStorageOptions {
inventory_base_uri: path.to_string_lossy().to_string(),
nodes_uri: "nodes".to_string(),
classes_uri: "classes".to_string(),
parameter_key_style: ParameterKeyStyle::None,
compose_node_name: false,
..YamlFsStorageOptions::default()
},
..StorageOptions::default()
};
load(&storage_options).expect("Failed to load e2e inventory")
}
fn load_python_compat_inventory() -> ferroclass::inventory::Inventory {
let path = python_compat_path();
let storage_options = StorageOptions {
storage_type: StorageType::YamlFs,
yaml_fs_options: YamlFsStorageOptions {
inventory_base_uri: path.to_string_lossy().to_string(),
nodes_uri: "nodes".to_string(),
classes_uri: "classes".to_string(),
parameter_key_style: ParameterKeyStyle::default(),
compose_node_name: false,
..YamlFsStorageOptions::default()
},
..StorageOptions::default()
};
load(&storage_options).expect("Failed to load python_compat inventory")
}
fn e2e_options() -> Options {
let path = e2e_inventory_path();
Options {
storage_options: StorageOptions {
storage_type: StorageType::YamlFs,
yaml_fs_options: YamlFsStorageOptions {
inventory_base_uri: path.to_string_lossy().to_string(),
nodes_uri: "nodes".to_string(),
classes_uri: "classes".to_string(),
parameter_key_style: ParameterKeyStyle::None,
compose_node_name: false,
..YamlFsStorageOptions::default()
},
..StorageOptions::default()
},
output_options: OutputOptions {
output: OutputFormat::JSON,
pretty_print: true,
output_sorted: false,
no_refs: false,
group_errors: true,
},
..Options::default()
}
}
#[test]
fn test_reclass_inventory_yaml_sorted() {
let inventory = load_e2e_inventory();
let mut output = InventoryOutput::from_inventory(&inventory, TEST_TIMESTAMP, false, false)
.expect("Failed to build inventory output");
output.sort_keys();
let yaml =
format_output(&output, OutputFormat::Yaml, true, true).expect("YAML formatting failed");
insta::assert_snapshot!("reclass_inventory_yaml_sorted", normalize_uris(&yaml));
}
#[test]
fn test_reclass_nodeinfo_yaml() {
let inventory = load_e2e_inventory();
let merged = inventory
.merge_node("web-prod-01")
.expect("Failed to merge node");
let output = NodeInfoOutput::from_node(&merged, TEST_TIMESTAMP);
let yaml =
format_output(&output, OutputFormat::Yaml, true, false).expect("YAML formatting failed");
insta::assert_snapshot!("reclass_nodeinfo_yaml", normalize_uris(&yaml));
}
#[test]
fn test_reclass_python_compat_inventory_yaml_sorted() {
let inventory = load_python_compat_inventory();
let mut output = InventoryOutput::from_inventory(&inventory, TEST_TIMESTAMP, false, false)
.expect("Failed to build inventory output");
output.sort_keys();
let yaml =
format_output(&output, OutputFormat::Yaml, true, true).expect("YAML formatting failed");
insta::assert_snapshot!(
"reclass_python_compat_inventory_yaml_sorted",
normalize_uris(&yaml)
);
}
#[test]
fn test_reclass_python_compat_nodeinfo_yaml() {
let inventory = load_python_compat_inventory();
let merged = inventory
.merge_node("reclass")
.expect("Failed to merge node");
let output = NodeInfoOutput::from_node(&merged, TEST_TIMESTAMP);
let yaml =
format_output(&output, OutputFormat::Yaml, true, false).expect("YAML formatting failed");
insta::assert_snapshot!("reclass_python_compat_nodeinfo_yaml", normalize_uris(&yaml));
}
#[test]
fn test_ansible_inventory_yaml() {
let config = e2e_options();
let inventory = ferroclass::output::ansible::build_inventory(&config, "_grp", TEST_TIMESTAMP)
.expect("Failed to build ansible inventory");
let yaml =
format_output(&inventory, OutputFormat::Yaml, true, true).expect("YAML formatting failed");
insta::assert_snapshot!("ansible_inventory_yaml", normalize_uris(&yaml));
}
#[test]
fn test_ansible_hostvars_yaml() {
let config = e2e_options();
let host_vars =
ferroclass::output::ansible::build_host_vars(&config, "web-prod-01", TEST_TIMESTAMP)
.expect("Failed to build host vars");
let yaml =
format_output(&host_vars, OutputFormat::Yaml, true, false).expect("YAML formatting failed");
insta::assert_snapshot!("ansible_hostvars_yaml", normalize_uris(&yaml));
}
#[test]
fn test_ansible_hostvars_inv_query_yaml() {
let config = e2e_options();
let host_vars =
ferroclass::output::ansible::build_host_vars(&config, "web-prod-02", TEST_TIMESTAMP)
.expect("Failed to build host vars for web-prod-02");
let yaml =
format_output(&host_vars, OutputFormat::Yaml, true, false).expect("YAML formatting failed");
insta::assert_snapshot!("ansible_hostvars_inv_query_yaml", normalize_uris(&yaml));
}
#[test]
fn test_salt_top_yaml() {
let config = e2e_options();
let top_data = ferroclass::output::salt::build_top(&config).expect("Failed to build salt top");
let yaml =
format_output(&top_data, OutputFormat::Yaml, true, true).expect("YAML formatting failed");
insta::assert_snapshot!("salt_top_yaml", normalize_uris(&yaml));
}
#[test]
fn test_salt_pillar_yaml() {
let config = e2e_options();
let pillar_data = ferroclass::output::salt::build_pillar(&config, "web-prod-01")
.expect("Failed to build pillar");
let yaml = format_output(&pillar_data, OutputFormat::Yaml, true, false)
.expect("YAML formatting failed");
insta::assert_snapshot!("salt_pillar_yaml", normalize_uris(&yaml));
}
#[test]
fn test_salt_top_json() {
let config = e2e_options();
let top_data = ferroclass::output::salt::build_top(&config).expect("Failed to build salt top");
let json =
format_output(&top_data, OutputFormat::JSON, true, true).expect("JSON formatting failed");
insta::assert_snapshot!("salt_top_json", normalize_uris(&json));
}
#[test]
fn test_salt_pillar_json() {
let config = e2e_options();
let pillar_data = ferroclass::output::salt::build_pillar(&config, "web-prod-01")
.expect("Failed to build pillar");
let json = format_output(&pillar_data, OutputFormat::JSON, true, false)
.expect("JSON formatting failed");
insta::assert_snapshot!("salt_pillar_json", normalize_uris(&json));
}
fn python_compat_options() -> Options {
let path = python_compat_path();
Options {
storage_options: StorageOptions {
storage_type: StorageType::YamlFs,
yaml_fs_options: YamlFsStorageOptions {
inventory_base_uri: path.to_string_lossy().to_string(),
nodes_uri: "nodes".to_string(),
classes_uri: "classes".to_string(),
parameter_key_style: ParameterKeyStyle::default(),
compose_node_name: false,
..YamlFsStorageOptions::default()
},
..StorageOptions::default()
},
output_options: OutputOptions {
output: OutputFormat::JSON,
pretty_print: true,
output_sorted: false,
no_refs: false,
group_errors: true,
},
..Options::default()
}
}
#[test]
fn test_salt_python_compat_top_yaml() {
let config = python_compat_options();
let top_data = ferroclass::output::salt::build_top(&config).expect("Failed to build salt top");
let yaml =
format_output(&top_data, OutputFormat::Yaml, true, true).expect("YAML formatting failed");
insta::assert_snapshot!("salt_python_compat_top_yaml", normalize_uris(&yaml));
}
#[test]
fn test_salt_python_compat_pillar_yaml() {
let config = python_compat_options();
let pillar_data =
ferroclass::output::salt::build_pillar(&config, "reclass").expect("Failed to build pillar");
let yaml = format_output(&pillar_data, OutputFormat::Yaml, true, false)
.expect("YAML formatting failed");
insta::assert_snapshot!("salt_python_compat_pillar_yaml", normalize_uris(&yaml));
}
#[test]
fn test_salt_pillar_node_not_found() {
let config = e2e_options();
let result = ferroclass::output::salt::build_pillar(&config, "nonexistent-node");
assert!(result.is_err(), "Expected error for nonexistent node");
let err = result.err().unwrap();
let msg = format!("{}", err);
assert!(
msg.contains("nonexistent-node"),
"Error message should mention the node name: {msg}"
);
}