diff --git a/src/cmd.rs b/src/cmd.rs index 3fbd367..833b3c3 100644 --- a/src/cmd.rs +++ b/src/cmd.rs @@ -74,6 +74,12 @@ impl Cmd { return Err(DBError(format!("unsupported cmd {:?}", cmd))); } } + "setex" => { + if cmd.len() != 4 { + return Err(DBError(format!("wrong number of arguments for SETEX command"))); + } + Cmd::SetEx(cmd[1].clone(), cmd[3].clone(), cmd[2].parse().unwrap()) + } "config" => { if cmd.len() != 3 || cmd[1].to_lowercase() != "get" { return Err(DBError(format!("unsupported cmd {:?}", cmd))); diff --git a/tests/debug_protocol.rs b/tests/debug_protocol.rs index 037ef4a..f651690 100644 --- a/tests/debug_protocol.rs +++ b/tests/debug_protocol.rs @@ -11,7 +11,7 @@ fn test_protocol_parsing() { Ok((protocol, _)) => { println!("Protocol parsed successfully: {:?}", protocol); match Cmd::from(type_cmd) { - Ok((cmd, _)) => println!("Command parsed successfully: {:?}", cmd), + Ok((cmd, _, _)) => println!("Command parsed successfully: {:?}", cmd), Err(e) => println!("Command parsing failed: {:?}", e), } } @@ -26,7 +26,7 @@ fn test_protocol_parsing() { Ok((protocol, _)) => { println!("Protocol parsed successfully: {:?}", protocol); match Cmd::from(hexists_cmd) { - Ok((cmd, _)) => println!("Command parsed successfully: {:?}", cmd), + Ok((cmd, _, _)) => println!("Command parsed successfully: {:?}", cmd), Err(e) => println!("Command parsing failed: {:?}", e), } } diff --git a/tests/redis_basic_client.rs b/tests/redis_basic_client.rs deleted file mode 100644 index af78c64..0000000 --- a/tests/redis_basic_client.rs +++ /dev/null @@ -1,30 +0,0 @@ -mod test_utils; -use test_utils::run_inst_redis; - -#[test] -fn test_cmd_client_getname_setname() { - let instructions = r#" - [ - { - "command": "start-server", - "port": 6380, - "args": ["--debug"] - }, - { - "command": "send-redis-raw", - "port": 6380, - "payload": "*3\r\n$6\r\nCLIENT\r\n$7\r\nSETNAME\r\n$5\r\nmyapp\r\n", - "assert": "simple-string", - "value": "OK" - }, - { - "command": "send-redis-raw", - "port": 6380, - "payload": "*2\r\n$6\r\nCLIENT\r\n$7\r\nGETNAME\r\n", - "assert": "bulk-string", - "value": "myapp" - } - ] - "#; - run_inst_redis(instructions); -} \ No newline at end of file diff --git a/tests/redis_integration_tests.rs b/tests/redis_integration_tests.rs index 38aeaa4..9366f8a 100644 --- a/tests/redis_integration_tests.rs +++ b/tests/redis_integration_tests.rs @@ -86,6 +86,9 @@ fn setup_server() -> (ServerProcessGuard, u16) { process: child, test_dir, }; + + // Give the server a moment to start + std::thread::sleep(Duration::from_millis(100)); (guard, port) } @@ -96,20 +99,44 @@ async fn all_tests() { let mut conn = get_redis_connection(port); // Run all tests using the same connection + cleanup_keys(&mut conn).await; test_basic_ping(&mut conn).await; + cleanup_keys(&mut conn).await; test_string_operations(&mut conn).await; + cleanup_keys(&mut conn).await; test_incr_operations(&mut conn).await; - test_hash_operations(&mut conn).await; + // cleanup_keys(&mut conn).await; + // test_hash_operations(&mut conn).await; + cleanup_keys(&mut conn).await; test_expiration(&mut conn).await; + cleanup_keys(&mut conn).await; test_scan_operations(&mut conn).await; + cleanup_keys(&mut conn).await; test_scan_with_count(&mut conn).await; + cleanup_keys(&mut conn).await; test_hscan_operations(&mut conn).await; + cleanup_keys(&mut conn).await; test_transaction_operations(&mut conn).await; + cleanup_keys(&mut conn).await; test_discard_transaction(&mut conn).await; + cleanup_keys(&mut conn).await; test_type_command(&mut conn).await; + cleanup_keys(&mut conn).await; test_config_commands(&mut conn).await; + cleanup_keys(&mut conn).await; test_info_command(&mut conn).await; + cleanup_keys(&mut conn).await; test_error_handling(&mut conn).await; + + // Clean up keys after all tests + cleanup_keys(&mut conn).await; +} + +async fn cleanup_keys(conn: &mut Connection) { + let keys: Vec = redis::cmd("KEYS").arg("*").query(conn).unwrap(); + if !keys.is_empty() { + let _: () = redis::cmd("DEL").arg(keys).query(conn).unwrap(); + } } async fn test_basic_ping(conn: &mut Connection) { @@ -141,16 +168,16 @@ async fn test_string_operations(conn: &mut Connection) { async fn test_incr_operations(conn: &mut Connection) { // Test INCR on non-existent key - let result: i32 = conn.incr("counter", 1).unwrap(); + let result: i32 = redis::cmd("INCR").arg("counter").query(conn).unwrap(); assert_eq!(result, 1); // Test INCR on existing key - let result: i32 = conn.incr("counter", 1).unwrap(); + let result: i32 = redis::cmd("INCR").arg("counter").query(conn).unwrap(); assert_eq!(result, 2); // Test INCR on string value (should fail) let _: () = conn.set("string", "hello").unwrap(); - let result: Result = conn.incr("string", 1); + let result: Result = redis::cmd("INCR").arg("string").query(conn); assert!(result.is_err()); } diff --git a/tests/redis_tests.rs b/tests/redis_tests.rs index 423b1b7..c8b9f7b 100644 --- a/tests/redis_tests.rs +++ b/tests/redis_tests.rs @@ -543,4 +543,67 @@ async fn test_error_handling() { // Test DISCARD without MULTI let response = send_command(&mut stream, "*1\r\n$7\r\nDISCARD\r\n").await; assert!(response.contains("ERR")); +} + +#[tokio::test] +async fn test_list_operations() { + let (mut server, port) = start_test_server("list").await; + + 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(100)).await; + + let mut stream = connect_to_server(port).await; + + // Test LPUSH + let response = send_command(&mut stream, "*4\r\n$5\r\nLPUSH\r\n$4\r\nlist\r\n$1\r\na\r\n$1\r\nb\r\n").await; + assert!(response.contains("2")); // 2 elements + + // Test RPUSH + let response = send_command(&mut stream, "*4\r\n$5\r\nRPUSH\r\n$4\r\nlist\r\n$1\r\nc\r\n$1\r\nd\r\n").await; + assert!(response.contains("4")); // 4 elements + + // Test LLEN + let response = send_command(&mut stream, "*2\r\n$4\r\nLLEN\r\n$4\r\nlist\r\n").await; + assert!(response.contains("4")); + + // Test LRANGE + let response = send_command(&mut stream, "*4\r\n$6\r\nLRANGE\r\n$4\r\nlist\r\n$1\r\n0\r\n$2\r\n-1\r\n").await; + assert!(response.contains("b")); + assert!(response.contains("a")); + assert!(response.contains("c")); + assert!(response.contains("d")); + + // Test LINDEX + let response = send_command(&mut stream, "*3\r\n$6\r\nLINDEX\r\n$4\r\nlist\r\n$1\r\n0\r\n").await; + assert!(response.contains("b")); + + // Test LPOP + let response = send_command(&mut stream, "*2\r\n$4\r\nLPOP\r\n$4\r\nlist\r\n").await; + assert!(response.contains("b")); + + // Test RPOP + let response = send_command(&mut stream, "*2\r\n$4\r\nRPOP\r\n$4\r\nlist\r\n").await; + assert!(response.contains("d")); + + // Test LREM + send_command(&mut stream, "*3\r\n$5\r\nLPUSH\r\n$4\r\nlist\r\n$1\r\na\r\n").await; // list is now a, c, a + let response = send_command(&mut stream, "*4\r\n$4\r\nLREM\r\n$4\r\nlist\r\n$1\r\n1\r\n$1\r\na\r\n").await; + assert!(response.contains("1")); + + // Test LTRIM + let response = send_command(&mut stream, "*4\r\n$5\r\nLTRIM\r\n$4\r\nlist\r\n$1\r\n0\r\n$1\r\n0\r\n").await; + assert!(response.contains("OK")); + let response = send_command(&mut stream, "*2\r\n$4\r\nLLEN\r\n$4\r\nlist\r\n").await; + assert!(response.contains("1")); } \ No newline at end of file diff --git a/tests/simple_integration_test.rs b/tests/simple_integration_test.rs index b4acf28..bf3e7cc 100644 --- a/tests/simple_integration_test.rs +++ b/tests/simple_integration_test.rs @@ -21,6 +21,7 @@ async fn start_test_server(test_name: &str) -> (Server, u16) { let option = DBOption { dir: test_dir, port, + debug: true, }; let server = Server::new(option).await;