use redis_rs::{server::Server, options::DBOption}; use std::time::Duration; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::net::TcpStream; use tokio::time::sleep; // Helper function to start a test server with clean data directory async fn start_test_server(test_name: &str) -> (Server, u16) { use std::sync::atomic::{AtomicU16, Ordering}; static PORT_COUNTER: AtomicU16 = AtomicU16::new(16500); let port = PORT_COUNTER.fetch_add(1, Ordering::SeqCst); let test_dir = format!("/tmp/herodb_simple_test_{}", test_name); // Clean up any existing test data let _ = std::fs::remove_dir_all(&test_dir); std::fs::create_dir_all(&test_dir).unwrap(); let option = DBOption { dir: test_dir, port, debug: false, databases: 16, }; let server = Server::new(option).await; (server, port) } // Helper function to send command and get response async fn send_command(stream: &mut TcpStream, command: &str) -> String { stream.write_all(command.as_bytes()).await.unwrap(); let mut buffer = [0; 1024]; let n = stream.read(&mut buffer).await.unwrap(); String::from_utf8_lossy(&buffer[..n]).to_string() } // Helper function to connect to the test server async fn connect_to_server(port: u16) -> TcpStream { let mut attempts = 0; loop { match TcpStream::connect(format!("127.0.0.1:{}", port)).await { Ok(stream) => return stream, Err(_) if attempts < 10 => { attempts += 1; sleep(Duration::from_millis(100)).await; } Err(e) => panic!("Failed to connect to test server: {}", e), } } } #[tokio::test] async fn test_basic_ping_simple() { let (mut server, port) = start_test_server("ping").await; // Start server in background tokio::spawn(async move { let listener = tokio::net::TcpListener::bind(format!("127.0.0.1:{}", port)) .await .unwrap(); loop { if let Ok((stream, _)) = listener.accept().await { let _ = server.handle(stream).await; } } }); sleep(Duration::from_millis(200)).await; let mut stream = connect_to_server(port).await; let response = send_command(&mut stream, "*1\r\n$4\r\nPING\r\n").await; assert!(response.contains("PONG")); } #[tokio::test] async fn test_hset_clean_db() { let (mut server, port) = start_test_server("hset_clean").await; // Start server in background tokio::spawn(async move { let listener = tokio::net::TcpListener::bind(format!("127.0.0.1:{}", port)) .await .unwrap(); loop { if let Ok((stream, _)) = listener.accept().await { let _ = server.handle(stream).await; } } }); sleep(Duration::from_millis(200)).await; let mut stream = connect_to_server(port).await; // Test HSET - should return 1 for new field let response = send_command(&mut stream, "*4\r\n$4\r\nHSET\r\n$4\r\nhash\r\n$6\r\nfield1\r\n$6\r\nvalue1\r\n").await; println!("HSET response: {}", response); assert!(response.contains("1"), "Expected HSET to return 1, got: {}", response); // Test HGET let response = send_command(&mut stream, "*3\r\n$4\r\nHGET\r\n$4\r\nhash\r\n$6\r\nfield1\r\n").await; println!("HGET response: {}", response); assert!(response.contains("value1")); } #[tokio::test] async fn test_type_command_simple() { let (mut server, port) = start_test_server("type").await; // Start server in background tokio::spawn(async move { let listener = tokio::net::TcpListener::bind(format!("127.0.0.1:{}", port)) .await .unwrap(); loop { if let Ok((stream, _)) = listener.accept().await { let _ = server.handle(stream).await; } } }); sleep(Duration::from_millis(200)).await; let mut stream = connect_to_server(port).await; // Test string type send_command(&mut stream, "*3\r\n$3\r\nSET\r\n$6\r\nstring\r\n$5\r\nvalue\r\n").await; let response = send_command(&mut stream, "*2\r\n$4\r\nTYPE\r\n$6\r\nstring\r\n").await; println!("TYPE string response: {}", response); assert!(response.contains("string")); // Test hash type send_command(&mut stream, "*4\r\n$4\r\nHSET\r\n$4\r\nhash\r\n$5\r\nfield\r\n$5\r\nvalue\r\n").await; let response = send_command(&mut stream, "*2\r\n$4\r\nTYPE\r\n$4\r\nhash\r\n").await; println!("TYPE hash response: {}", response); assert!(response.contains("hash")); // Test non-existent key let response = send_command(&mut stream, "*2\r\n$4\r\nTYPE\r\n$7\r\nnoexist\r\n").await; println!("TYPE noexist response: {}", response); assert!(response.contains("none"), "Expected 'none' for non-existent key, got: {}", response); } #[tokio::test] async fn test_hexists_simple() { let (mut server, port) = start_test_server("hexists").await; // Start server in background tokio::spawn(async move { let listener = tokio::net::TcpListener::bind(format!("127.0.0.1:{}", port)) .await .unwrap(); loop { if let Ok((stream, _)) = listener.accept().await { let _ = server.handle(stream).await; } } }); sleep(Duration::from_millis(200)).await; let mut stream = connect_to_server(port).await; // Set up hash send_command(&mut stream, "*4\r\n$4\r\nHSET\r\n$4\r\nhash\r\n$6\r\nfield1\r\n$6\r\nvalue1\r\n").await; // Test HEXISTS for existing field let response = send_command(&mut stream, "*3\r\n$7\r\nHEXISTS\r\n$4\r\nhash\r\n$6\r\nfield1\r\n").await; println!("HEXISTS existing field response: {}", response); assert!(response.contains("1")); // Test HEXISTS for non-existent field let response = send_command(&mut stream, "*3\r\n$7\r\nHEXISTS\r\n$4\r\nhash\r\n$7\r\nnoexist\r\n").await; println!("HEXISTS non-existent field response: {}", response); assert!(response.contains("0"), "Expected HEXISTS to return 0 for non-existent field, got: {}", response); }