//! 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()) } }