//! Integration tests for Chainfire //! //! These tests verify that the server, client, and all components work together correctly. use chainfire_client::Client; use chainfire_server::{ config::{ClusterConfig, NetworkConfig, NodeConfig, RaftConfig, ServerConfig, StorageConfig}, server::Server, }; use std::time::Duration; use tokio::time::sleep; /// Create a test server configuration fn test_config(port: u16) -> (ServerConfig, tempfile::TempDir) { use std::net::SocketAddr; let api_addr: SocketAddr = format!("127.0.0.1:{}", port).parse().unwrap(); let raft_addr: SocketAddr = format!("127.0.0.1:{}", port + 100).parse().unwrap(); let gossip_addr: SocketAddr = format!("127.0.0.1:{}", port + 200).parse().unwrap(); let temp_dir = tempfile::tempdir().unwrap(); let config = ServerConfig { node: NodeConfig { id: 1, name: format!("test-node-{}", port), role: "control_plane".to_string(), }, cluster: ClusterConfig { id: 1, bootstrap: true, initial_members: vec![], }, network: NetworkConfig { api_addr, raft_addr, gossip_addr, tls: None, }, storage: StorageConfig { data_dir: temp_dir.path().to_path_buf(), }, raft: RaftConfig::default(), }; (config, temp_dir) } #[tokio::test] async fn test_single_node_kv_operations() { // Start server let (config, _temp_dir) = test_config(23790); let api_addr = config.network.api_addr; let server = Server::new(config).await.unwrap(); // Run server in background let server_handle = tokio::spawn(async move { let _ = server.run().await; }); // Wait for server to start and Raft leader election // Increased from 500ms to 2000ms for CI/constrained environments sleep(Duration::from_millis(2000)).await; // Connect client let mut client = Client::connect(format!("http://{}", api_addr)) .await .unwrap(); // Test put with retry (leader election may still be in progress) let mut rev = 0; for attempt in 0..5 { match client.put("test/key1", "value1").await { Ok(r) => { rev = r; break; } Err(e) if attempt < 4 => { eprintln!("Put attempt {} failed: {}, retrying...", attempt + 1, e); sleep(Duration::from_millis(500)).await; } Err(e) => panic!("Put failed after 5 attempts: {}", e), } } assert!(rev > 0); // Test get let value = client.get("test/key1").await.unwrap(); assert_eq!(value, Some(b"value1".to_vec())); // Test put with different value let rev2 = client.put("test/key1", "value2").await.unwrap(); assert!(rev2 > rev); // Test get updated value let value = client.get("test/key1").await.unwrap(); assert_eq!(value, Some(b"value2".to_vec())); // Test get non-existent key let value = client.get("test/nonexistent").await.unwrap(); assert!(value.is_none()); // Test delete let deleted = client.delete("test/key1").await.unwrap(); assert!(deleted); // Verify deletion let value = client.get("test/key1").await.unwrap(); assert!(value.is_none()); // Test delete non-existent key let deleted = client.delete("test/nonexistent").await.unwrap(); assert!(!deleted); // Test prefix operations client.put("prefix/a", "1").await.unwrap(); client.put("prefix/b", "2").await.unwrap(); client.put("prefix/c", "3").await.unwrap(); client.put("other/key", "other").await.unwrap(); let prefix_values = client.get_prefix("prefix/").await.unwrap(); assert_eq!(prefix_values.len(), 3); // Cleanup server_handle.abort(); } #[tokio::test] async fn test_cluster_status() { let (config, _temp_dir) = test_config(23800); let api_addr = config.network.api_addr; let server = Server::new(config).await.unwrap(); let server_handle = tokio::spawn(async move { let _ = server.run().await; }); sleep(Duration::from_millis(500)).await; let mut client = Client::connect(format!("http://{}", api_addr)) .await .unwrap(); let status = client.status().await.unwrap(); assert_eq!(status.leader, 1); assert!(status.raft_term > 0); server_handle.abort(); } #[tokio::test] async fn test_string_convenience_methods() { let (config, _temp_dir) = test_config(23810); let api_addr = config.network.api_addr; let server = Server::new(config).await.unwrap(); let server_handle = tokio::spawn(async move { let _ = server.run().await; }); sleep(Duration::from_millis(500)).await; let mut client = Client::connect(format!("http://{}", api_addr)) .await .unwrap(); // Test string methods client.put_str("/config/name", "chainfire").await.unwrap(); let value = client.get_str("/config/name").await.unwrap(); assert_eq!(value, Some("chainfire".to_string())); server_handle.abort(); }