203 lines
6.6 KiB
Rust
203 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())
|
||
|
|
}
|
||
|
|
}
|