/* * Author: Windmill Labs, Inc * Copyright (C) Windmill Labs, Inc - All Rights Reserved * Unauthorized copying of this file, via any medium is strictly prohibited. */ //! Integration tests for Azure Key Vault secret backend //! //! These tests require a running Azure Key Vault emulator. //! //! ## Setup //! //! 1. Run the emulator (james-gould/azure-keyvault-emulator): //! //! ```bash //! docker run -d -p 4997:4997 \ //! -e Persist=true \ //! --name azure-kv-emulator \ //! jamesgoulddev/azure-keyvault-emulator:latest //! ``` //! //! 2. The emulator uses HTTPS with a self-signed cert. You may need to either: //! - Trust the emulator's certificate, or //! - Set `AZURE_KV_ALLOW_INSECURE=true` to skip TLS verification (test only) //! //! 3. Run the tests: //! //! ```bash //! RUN_AZURE_KV_TESTS=1 cargo test -p windmill-common --features private,enterprise \ //! azure_kv_integration -- --nocapture //! ``` //! //! ## Emulator authentication //! //! The emulator accepts any well-formed JWT token without verifying signatures. //! A static dummy token is used for testing. #[cfg(all(feature = "private", feature = "enterprise"))] mod tests { use windmill_common::secret_backend::{ test_azure_kv_connection, AzureKeyVaultBackend, AzureKeyVaultSettings, SecretBackend, }; fn should_run_azure_kv_tests() -> bool { std::env::var("RUN_AZURE_KV_TESTS") .map(|v| v == "1" || v.to_lowercase() == "true") .unwrap_or(false) } macro_rules! skip_if_no_azure_kv { () => { if !should_run_azure_kv_tests() { println!("Skipping test: RUN_AZURE_KV_TESTS=1 not set"); println!("To run Azure KV tests: RUN_AZURE_KV_TESTS=1 cargo test ..."); return; } }; } /// A minimal valid JWT token (the emulator doesn't verify signatures). /// Format: base64(header).base64(payload).base64(signature) fn dummy_jwt_token() -> String { std::env::var("AZURE_KV_TOKEN").unwrap_or_else(|_| { // Minimal JWT: {"alg":"HS256","typ":"JWT"}.{"sub":"test","exp":9999999999}.signature "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0ZXN0IiwiZXhwIjo5OTk5OTk5OTk5fQ.test-signature".to_string() }) } fn azure_kv_settings() -> AzureKeyVaultSettings { AzureKeyVaultSettings { vault_url: std::env::var("AZURE_KV_URL") .unwrap_or_else(|_| "https://localhost:4997".to_string()), tenant_id: "test-tenant".to_string(), client_id: "test-client".to_string(), client_secret: None, token: Some(dummy_jwt_token()), } } fn create_backend() -> AzureKeyVaultBackend { AzureKeyVaultBackend::new(azure_kv_settings()) } #[tokio::test] async fn test_azure_kv_connection_with_token() { skip_if_no_azure_kv!(); let settings = azure_kv_settings(); println!("Testing Azure KV connection..."); println!(" URL: {}", settings.vault_url); let result = test_azure_kv_connection(&settings).await; assert!( result.is_ok(), "Failed to connect to Azure KV: {:?}", result.err() ); println!(" Connected successfully"); } #[tokio::test] async fn test_azure_kv_crud() { skip_if_no_azure_kv!(); let backend = create_backend(); let workspace_id = "test-crud"; let path = "test-secret"; let value = "my-super-secret-value-123"; println!("Testing Azure KV CRUD..."); // Create println!(" Creating secret..."); backend .set_secret(workspace_id, path, value) .await .expect("Failed to create secret"); println!(" Created"); // Read println!(" Reading secret..."); let read_value = backend .get_secret(workspace_id, path) .await .expect("Failed to read secret"); assert_eq!(read_value, value); println!(" Read (value matches)"); // Update (write new version) println!(" Updating secret..."); let new_value = "updated-secret-value-456"; backend .set_secret(workspace_id, path, new_value) .await .expect("Failed to update secret"); // NOTE: The james-gould emulator has a known bug where GET without a // version returns the first version instead of the latest. Against real // Azure Key Vault, this read would return new_value. We skip the update // assertion for emulator compatibility. let updated = backend .get_secret(workspace_id, path) .await .expect("Failed to read updated secret"); println!( " Updated (read back: {}, emulator may return stale version)", updated ); // Delete println!(" Deleting secret..."); backend .delete_secret(workspace_id, path) .await .expect("Failed to delete secret"); let result = backend.get_secret(workspace_id, path).await; assert!(result.is_err(), "Expected NotFound after delete"); println!(" Deleted"); println!("CRUD test passed"); } #[tokio::test] async fn test_azure_kv_not_found() { skip_if_no_azure_kv!(); let backend = create_backend(); let result = backend .get_secret("nonexistent-workspace", "nonexistent-path") .await; assert!(result.is_err(), "Expected error for nonexistent secret"); println!("NotFound test passed"); } #[tokio::test] async fn test_azure_kv_secret_name_encoding() { skip_if_no_azure_kv!(); let backend = create_backend(); let workspace_id = "demo"; let path = "u/admin/my_secret_key"; let value = "encoded-name-test-value"; println!("Testing secret name encoding..."); println!(" workspace: {}, path: {}", workspace_id, path); // Write with special characters in path backend .set_secret(workspace_id, path, value) .await .expect("Failed to create secret with encoded name"); // Read back let read_value = backend .get_secret(workspace_id, path) .await .expect("Failed to read secret with encoded name"); assert_eq!(read_value, value); // Cleanup backend .delete_secret(workspace_id, path) .await .expect("Failed to delete secret"); println!(" Encoding test passed"); } }