prvng_platform/crates/provisioning-tool/tests/e2e.rs

202 lines
6.6 KiB
Rust

//! End-to-end tests for `provisioning-tool` library surface.
//!
//! These tests exercise the registry + tool dispatch without requiring a running
//! orchestrator, vault, or hcloud CLI — only pure-Rust code paths.
use provisioning_core::{
Environment,
sources::{NclCache, OrchestratorClient},
providers::hcloud::HcloudClient,
Registry,
tool::Context,
};
use provisioning_tool::{build_env, print_json};
use serde_json::json;
use std::sync::Arc;
fn make_registry() -> Registry {
let cache = Arc::new(NclCache::new());
let orch = Arc::new(OrchestratorClient::new("http://localhost:19999"));
let hcloud = Arc::new(HcloudClient::new());
Registry::with_all_tools(cache, orch, hcloud, None).expect("registry init")
}
fn make_ctx() -> Context {
Context::new(Arc::new(Environment::default()))
}
// ── Registry shape ────────────────────────────────────────────────────────────
#[test]
fn list_returns_52_tools_without_vault() {
let reg = make_registry();
assert_eq!(reg.len(), 52);
}
#[test]
fn list_is_sorted_and_non_empty() {
let reg = make_registry();
let names: Vec<&str> = reg.list().iter().map(|m| m.name).collect();
assert!(!names.is_empty());
let mut sorted = names.clone();
sorted.sort_unstable();
assert_eq!(names, sorted);
}
#[test]
fn schema_of_known_tool_has_object_type() {
let reg = make_registry();
let schema = reg.schema_of("workspace_list").expect("workspace_list must exist");
assert_eq!(schema["type"], "object");
}
#[test]
fn schema_of_unknown_tool_returns_none() {
let reg = make_registry();
assert!(reg.schema_of("nonexistent_tool").is_none());
}
#[test]
fn get_known_tool_returns_some() {
let reg = make_registry();
assert!(reg.get("installer_settings_defaults").is_some());
}
#[test]
fn get_unknown_tool_returns_none() {
let reg = make_registry();
assert!(reg.get("no_such_tool").is_none());
}
// ── Pure-Rust tool dispatch ───────────────────────────────────────────────────
#[tokio::test]
async fn installer_defaults_solo_returns_valid_json() {
let reg = make_registry();
let ctx = make_ctx();
let result = reg
.invoke("installer_settings_defaults", json!({"mode": "solo"}), &ctx)
.await
.expect("invoke failed");
assert_eq!(result["mode"], "solo");
assert!(result["orchestrator"]["port"].is_number());
}
#[tokio::test]
async fn installer_defaults_enterprise_returns_valid_json() {
let reg = make_registry();
let ctx = make_ctx();
let result = reg
.invoke("installer_settings_defaults", json!({"mode": "enterprise"}), &ctx)
.await
.expect("invoke failed");
assert_eq!(result["mode"], "enterprise");
assert_eq!(result["nats"]["cluster"], true);
}
#[tokio::test]
async fn installer_defaults_unknown_mode_returns_error() {
let reg = make_registry();
let ctx = make_ctx();
let err = reg
.invoke("installer_settings_defaults", json!({"mode": "unknown_xyz"}), &ctx)
.await
.unwrap_err();
assert!(matches!(err, provisioning_core::ToolError::InvalidParam { .. }));
}
#[tokio::test]
async fn installer_settings_validate_without_config_returns_valid_json() {
let reg = make_registry();
let ctx = make_ctx();
let result = reg
.invoke("installer_settings_validate", json!({}), &ctx)
.await
.expect("invoke failed");
// Should return a json object with 'valid' and 'issues' fields
assert!(result["issues"].is_array());
}
#[tokio::test]
async fn invoke_missing_tool_returns_not_found() {
let reg = make_registry();
let ctx = make_ctx();
let err = reg
.invoke("nonexistent_tool", json!({}), &ctx)
.await
.unwrap_err();
assert!(matches!(err, provisioning_core::ToolError::NotFound(_)));
}
#[tokio::test]
async fn infra_detect_with_project_root_returns_detections() {
let reg = make_registry();
let mut env = Environment::default();
// Point to the platform dir which has Cargo.toml, justfile, etc.
env.workspaces_root = std::path::PathBuf::from(
std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".into()),
)
.join("../..");
let ctx = Context::new(Arc::new(env));
let result = reg
.invoke(
"infra_detect",
json!({ "path": env::manifest_dir_parent() }),
&ctx,
)
.await
.expect("infra_detect failed");
assert!(result["detections"].is_array());
}
#[tokio::test]
async fn ontology_graph_with_missing_root_returns_error() {
let reg = make_registry();
let mut env = Environment::default();
env.ontology_root = std::path::PathBuf::from("/tmp/no_such_ontology_dir_xyz");
let ctx = Context::new(Arc::new(env));
let err = reg
.invoke("ontology_graph", json!({}), &ctx)
.await
.unwrap_err();
assert!(matches!(
err,
provisioning_core::ToolError::NotFound(_) | provisioning_core::ToolError::Invocation(_)
));
}
// ── Output helpers ────────────────────────────────────────────────────────────
#[test]
fn print_json_compact_does_not_panic() {
let v = json!({"key": "value", "n": 42});
// Just verify it doesn't panic — output goes to stdout
print_json(&v, true);
print_json(&v, false);
}
// ── build_env structure ───────────────────────────────────────────────────────
#[test]
fn build_env_returns_valid_struct() {
// Verify build_env produces a structurally valid Environment without asserting
// specific values (env vars may be set by CI or other tests in parallel).
let env = build_env();
assert!(!env.orchestrator_url.is_empty());
assert!(env.orchestrator_url.starts_with("http"));
}
// ── helpers ───────────────────────────────────────────────────────────────────
mod env {
pub fn manifest_dir_parent() -> String {
let manifest = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".into());
std::path::PathBuf::from(manifest)
.parent()
.and_then(|p| p.parent())
.map(|p| p.to_string_lossy().into_owned())
.unwrap_or_else(|| ".".into())
}
}