diff --git a/src/commands/hset.rs b/src/commands/hset.rs new file mode 100644 index 0000000..cf75679 --- /dev/null +++ b/src/commands/hset.rs @@ -0,0 +1,159 @@ +//! Abstraction of HSET command. +//! +//! For general information about this command, see the [Redis documentation](). +//! +//! # Using command object +//! ``` +//!# use core::str::FromStr; +//!# use embedded_nal::SocketAddr; +//!# use std_embedded_nal::Stack; +//!# use std_embedded_time::StandardClock; +//!# use embedded_redis::commands::builder::CommandBuilder; +//! use embedded_redis::commands::hset::HashSetCommand; +//!# use embedded_redis::commands::publish::PublishCommand; +//!# use embedded_redis::network::ConnectionHandler; +//!# +//! let mut stack = Stack::default(); +//! let clock = StandardClock::default(); +//! +//! let mut connection_handler = ConnectionHandler::resp2(SocketAddr::from_str("127.0.0.1:6379").unwrap()); +//! let client = connection_handler.connect(&mut stack, Some(&clock)).unwrap(); +//!# client.send(CommandBuilder::new("DEL").arg_static("my_hash").to_command()).unwrap().wait().unwrap(); +//! +//! let command = HashSetCommand::new("my_hash".into(), "color".into(), "green".into()); +//! let response = client.send(command).unwrap().wait().unwrap(); +//! +//! // Returns the number of added fields +//! assert_eq!(1, response) +//! ``` +//! # Setting multiple fields at once +//! ``` +//!# use core::str::FromStr; +//!# use embedded_nal::SocketAddr; +//!# use std_embedded_nal::Stack; +//!# use std_embedded_time::StandardClock; +//!# use embedded_redis::commands::builder::CommandBuilder; +//!# use embedded_redis::commands::hset::HashSetCommand; +//!# use embedded_redis::commands::publish::PublishCommand; +//!# use embedded_redis::network::ConnectionHandler; +//!# +//!# let mut stack = Stack::default(); +//!# let clock = StandardClock::default(); +//!# +//!# let mut connection_handler = ConnectionHandler::resp2(SocketAddr::from_str("127.0.0.1:6379").unwrap()); +//!# let client = connection_handler.connect(&mut stack, Some(&clock)).unwrap(); +//!# client.send(CommandBuilder::new("DEL").arg_static("my_hash").to_command()).unwrap().wait().unwrap(); +//!# +//! let command = HashSetCommand::multiple("my_hash".into(), [ +//! ("color".into(), "green".into()), +//! ("material".into(), "stone".into()) +//! ]); +//! let response = client.send(command).unwrap().wait().unwrap(); +//! +//! // Returns the number of added fields +//! assert_eq!(2, response) +//! ``` +//! # Shorthand +//! [Client](Client#method.hset) provides a shorthand method for this command. +//! ``` +//!# use core::str::FromStr; +//!# use bytes::Bytes; +//!# use embedded_nal::SocketAddr; +//!# use std_embedded_nal::Stack; +//!# use std_embedded_time::StandardClock; +//!# use embedded_redis::commands::set::SetCommand; +//!# use embedded_redis::network::ConnectionHandler; +//!# +//!# let mut stack = Stack::default(); +//!# let clock = StandardClock::default(); +//!# +//!# let mut connection_handler = ConnectionHandler::resp2(SocketAddr::from_str("127.0.0.1:6379").unwrap()); +//!# let client = connection_handler.connect(&mut stack, Some(&clock)).unwrap(); +//!# +//!# let _ = client.send(SetCommand::new("test_key", "test_value")).unwrap().wait(); +//!# +//! // Using &str arguments +//! let _ = client.hset("hash", "field", "value"); +//! +//! // Using String arguments +//! let _ = client.hset("hash".to_string(), "field".to_string(), "value".to_string()); +//! +//! // Using Bytes arguments +//! let _ = client.hset(Bytes::from_static(b"hash"), Bytes::from_static(b"field"), Bytes::from_static(b"value")); +//! ``` +use crate::commands::auth::AuthCommand; +use crate::commands::builder::{CommandBuilder, ToInteger}; +use crate::commands::hello::HelloCommand; +use crate::commands::{Command, ResponseTypeError}; +use crate::network::protocol::Protocol; +use crate::network::{Client, CommandErrors, Future}; +use bytes::Bytes; +use embedded_nal::TcpClientStack; +use embedded_time::Clock; + +/// Abstraction of HSET command +pub struct HashSetCommand { + /// Hash key + key: Bytes, + + /// Field/Value paris + fields: [(Bytes, Bytes); N], +} + +impl HashSetCommand<1> { + pub fn new(key: Bytes, field: Bytes, value: Bytes) -> Self { + Self { + key, + fields: [(field, value)], + } + } +} + +impl HashSetCommand { + /// Constructs a new command with multiple field/value paris + pub fn multiple(key: Bytes, fields: [(Bytes, Bytes); N]) -> Self { + Self { key, fields } + } +} + +impl + ToInteger, const N: usize> Command for HashSetCommand { + type Response = i64; + + fn encode(&self) -> F { + let mut builder = CommandBuilder::new("HSET").arg(&self.key); + + for (field, value) in &self.fields { + builder = builder.arg(field).arg(value); + } + + builder.into() + } + + fn eval_response(&self, frame: F) -> Result { + frame.to_integer().ok_or(ResponseTypeError {}) + } +} + +impl<'a, N: TcpClientStack, C: Clock, P: Protocol> Client<'a, N, C, P> +where + AuthCommand: Command<::FrameType>, + HelloCommand: Command<::FrameType>, +{ + /// Shorthand for [HashSetCommand] + /// For setting multiple fields, use [HashSetCommand] directly instead + pub fn hset( + &'a self, + key: K, + field: F, + value: V, + ) -> Result>, CommandErrors> + where + Bytes: From, + Bytes: From, + Bytes: From, + ::FrameType: ToInteger, + ::FrameType: From, + { + self.send(HashSetCommand::new(key.into(), field.into(), value.into())) + } +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 4156123..27af5ad 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -5,10 +5,10 @@ pub mod custom; pub mod get; pub mod hello; pub mod helpers; +pub mod hset; pub mod ping; pub mod publish; pub mod set; - #[cfg(test)] pub(crate) mod tests; diff --git a/src/commands/tests/hset.rs b/src/commands/tests/hset.rs new file mode 100644 index 0000000..4cf2709 --- /dev/null +++ b/src/commands/tests/hset.rs @@ -0,0 +1,113 @@ +use crate::commands::hset::HashSetCommand; +use crate::commands::Command; +use redis_protocol::resp2::types::Frame as Resp2Frame; +use redis_protocol::resp3::types::Frame as Resp3Frame; + +#[test] +fn test_encode_single_field_resp2() { + let frame: Resp2Frame = HashSetCommand::new("my_hash".into(), "color".into(), "green".into()).encode(); + + assert!(frame.is_array()); + if let Resp2Frame::Array(array) = frame { + assert_eq!(4, array.len()); + assert_eq!("HSET", array[0].to_string().unwrap()); + assert_eq!("my_hash", array[1].to_string().unwrap()); + assert_eq!("color", array[2].to_string().unwrap()); + assert_eq!("green", array[3].to_string().unwrap()); + } +} + +#[test] +fn test_encode_single_field_resp3() { + let frame: Resp3Frame = HashSetCommand::new("my_hash".into(), "color".into(), "green".into()).encode(); + + if let Resp3Frame::Array { data, attributes: _ } = frame { + assert_eq!(4, data.len()); + assert_eq!("HSET", data[0].to_string().unwrap()); + assert_eq!("my_hash", data[1].to_string().unwrap()); + assert_eq!("color", data[2].to_string().unwrap()); + assert_eq!("green", data[3].to_string().unwrap()); + } +} + +#[test] +fn test_encode_multiple_fields_resp2() { + let frame: Resp2Frame = HashSetCommand::multiple( + "my_hash".into(), + [ + ("gender".into(), "male".into()), + ("material".into(), "wood".into()), + ], + ) + .encode(); + + if let Resp2Frame::Array(array) = frame { + assert_eq!(6, array.len()); + assert_eq!("HSET", array[0].to_string().unwrap()); + assert_eq!("my_hash", array[1].to_string().unwrap()); + assert_eq!("gender", array[2].to_string().unwrap()); + assert_eq!("male", array[3].to_string().unwrap()); + assert_eq!("material", array[4].to_string().unwrap()); + assert_eq!("wood", array[5].to_string().unwrap()); + } +} + +#[test] +fn test_encode_multiple_fields_resp3() { + let frame: Resp3Frame = HashSetCommand::multiple( + "my_hash".into(), + [ + ("gender".into(), "male".into()), + ("material".into(), "wood".into()), + ], + ) + .encode(); + + if let Resp3Frame::Array { data, attributes: _ } = frame { + assert_eq!(6, data.len()); + assert_eq!("HSET", data[0].to_string().unwrap()); + assert_eq!("my_hash", data[1].to_string().unwrap()); + assert_eq!("gender", data[2].to_string().unwrap()); + assert_eq!("male", data[3].to_string().unwrap()); + assert_eq!("material", data[4].to_string().unwrap()); + assert_eq!("wood", data[5].to_string().unwrap()); + } +} + +#[test] +fn test_eval_response_resp2_success() { + let command = HashSetCommand::new("my_hash".into(), "color".into(), "green".into()); + let response = command.eval_response(Resp2Frame::Integer(2)); + + assert_eq!(2, response.unwrap()); +} + +#[test] +fn test_eval_response_resp3_success() { + let command = HashSetCommand::new("my_hash".into(), "color".into(), "green".into()); + let response = command.eval_response(Resp3Frame::Number { + data: 3, + attributes: None, + }); + + assert_eq!(3, response.unwrap()); +} + +#[test] +fn test_eval_response_resp2_invalid_response() { + let command = HashSetCommand::new("my_hash".into(), "color".into(), "green".into()); + let response = command.eval_response(Resp2Frame::BulkString("3".into())); + + assert!(response.is_err()); +} + +#[test] +fn test_eval_response_resp3_invalid_response() { + let command = HashSetCommand::new("my_hash".into(), "color".into(), "green".into()); + let response = command.eval_response(Resp3Frame::BlobString { + data: "test".into(), + attributes: None, + }); + + assert!(response.is_err()); +} diff --git a/src/commands/tests/mod.rs b/src/commands/tests/mod.rs index 3bb5d61..1d3297a 100644 --- a/src/commands/tests/mod.rs +++ b/src/commands/tests/mod.rs @@ -3,6 +3,7 @@ mod bgsave; mod custom; mod get; pub(crate) mod hello; +mod hset; mod ping; mod publish; mod set; diff --git a/src/network/tests/client.rs b/src/network/tests/client.rs index 6ee25c6..f50de27 100644 --- a/src/network/tests/client.rs +++ b/src/network/tests/client.rs @@ -988,3 +988,69 @@ fn test_shorthand_bgsave_scheduled() { client.bgsave(true).unwrap().wait().unwrap(); } + +#[test] +fn test_shorthand_hset_str_argument() { + let clock = TestClock::new(vec![]); + + let mut network = NetworkMockBuilder::default() + .send( + 164, + "*4\r\n$4\r\nHSET\r\n$7\r\nmy_hash\r\n$5\r\ncolor\r\n$5\r\ngreen\r\n", + ) + .response(":1\r\n") + .into_mock(); + + let mut socket = SocketMock::new(164); + let client = create_mocked_client(&mut network, &mut socket, &clock, Resp2 {}); + + client.hset("my_hash", "color", "green").unwrap().wait().unwrap(); +} + +#[test] +fn test_shorthand_hset_string_argument() { + let clock = TestClock::new(vec![]); + + let mut network = NetworkMockBuilder::default() + .send( + 164, + "*4\r\n$4\r\nHSET\r\n$7\r\nmy_hash\r\n$5\r\ncolor\r\n$5\r\ngreen\r\n", + ) + .response(":1\r\n") + .into_mock(); + + let mut socket = SocketMock::new(164); + let client = create_mocked_client(&mut network, &mut socket, &clock, Resp2 {}); + + client + .hset("my_hash".to_string(), "color".to_string(), "green".to_string()) + .unwrap() + .wait() + .unwrap(); +} + +#[test] +fn test_shorthand_hset_bytes_argument() { + let clock = TestClock::new(vec![]); + + let mut network = NetworkMockBuilder::default() + .send( + 164, + "*4\r\n$4\r\nHSET\r\n$7\r\nmy_hash\r\n$5\r\ncolor\r\n$5\r\ngreen\r\n", + ) + .response(":1\r\n") + .into_mock(); + + let mut socket = SocketMock::new(164); + let client = create_mocked_client(&mut network, &mut socket, &clock, Resp2 {}); + + client + .hset( + Bytes::from_static(b"my_hash"), + Bytes::from_static(b"color"), + Bytes::from_static(b"green"), + ) + .unwrap() + .wait() + .unwrap(); +}
::FrameType>, + HelloCommand: Command<
::FrameType>, +{ + /// Shorthand for [HashSetCommand] + /// For setting multiple fields, use [HashSetCommand] directly instead + pub fn hset( + &'a self, + key: K, + field: F, + value: V, + ) -> Result>, CommandErrors> + where + Bytes: From, + Bytes: From, + Bytes: From, + ::FrameType: ToInteger, + ::FrameType: From, + { + self.send(HashSetCommand::new(key.into(), field.into(), value.into())) + } +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 4156123..27af5ad 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -5,10 +5,10 @@ pub mod custom; pub mod get; pub mod hello; pub mod helpers; +pub mod hset; pub mod ping; pub mod publish; pub mod set; - #[cfg(test)] pub(crate) mod tests; diff --git a/src/commands/tests/hset.rs b/src/commands/tests/hset.rs new file mode 100644 index 0000000..4cf2709 --- /dev/null +++ b/src/commands/tests/hset.rs @@ -0,0 +1,113 @@ +use crate::commands::hset::HashSetCommand; +use crate::commands::Command; +use redis_protocol::resp2::types::Frame as Resp2Frame; +use redis_protocol::resp3::types::Frame as Resp3Frame; + +#[test] +fn test_encode_single_field_resp2() { + let frame: Resp2Frame = HashSetCommand::new("my_hash".into(), "color".into(), "green".into()).encode(); + + assert!(frame.is_array()); + if let Resp2Frame::Array(array) = frame { + assert_eq!(4, array.len()); + assert_eq!("HSET", array[0].to_string().unwrap()); + assert_eq!("my_hash", array[1].to_string().unwrap()); + assert_eq!("color", array[2].to_string().unwrap()); + assert_eq!("green", array[3].to_string().unwrap()); + } +} + +#[test] +fn test_encode_single_field_resp3() { + let frame: Resp3Frame = HashSetCommand::new("my_hash".into(), "color".into(), "green".into()).encode(); + + if let Resp3Frame::Array { data, attributes: _ } = frame { + assert_eq!(4, data.len()); + assert_eq!("HSET", data[0].to_string().unwrap()); + assert_eq!("my_hash", data[1].to_string().unwrap()); + assert_eq!("color", data[2].to_string().unwrap()); + assert_eq!("green", data[3].to_string().unwrap()); + } +} + +#[test] +fn test_encode_multiple_fields_resp2() { + let frame: Resp2Frame = HashSetCommand::multiple( + "my_hash".into(), + [ + ("gender".into(), "male".into()), + ("material".into(), "wood".into()), + ], + ) + .encode(); + + if let Resp2Frame::Array(array) = frame { + assert_eq!(6, array.len()); + assert_eq!("HSET", array[0].to_string().unwrap()); + assert_eq!("my_hash", array[1].to_string().unwrap()); + assert_eq!("gender", array[2].to_string().unwrap()); + assert_eq!("male", array[3].to_string().unwrap()); + assert_eq!("material", array[4].to_string().unwrap()); + assert_eq!("wood", array[5].to_string().unwrap()); + } +} + +#[test] +fn test_encode_multiple_fields_resp3() { + let frame: Resp3Frame = HashSetCommand::multiple( + "my_hash".into(), + [ + ("gender".into(), "male".into()), + ("material".into(), "wood".into()), + ], + ) + .encode(); + + if let Resp3Frame::Array { data, attributes: _ } = frame { + assert_eq!(6, data.len()); + assert_eq!("HSET", data[0].to_string().unwrap()); + assert_eq!("my_hash", data[1].to_string().unwrap()); + assert_eq!("gender", data[2].to_string().unwrap()); + assert_eq!("male", data[3].to_string().unwrap()); + assert_eq!("material", data[4].to_string().unwrap()); + assert_eq!("wood", data[5].to_string().unwrap()); + } +} + +#[test] +fn test_eval_response_resp2_success() { + let command = HashSetCommand::new("my_hash".into(), "color".into(), "green".into()); + let response = command.eval_response(Resp2Frame::Integer(2)); + + assert_eq!(2, response.unwrap()); +} + +#[test] +fn test_eval_response_resp3_success() { + let command = HashSetCommand::new("my_hash".into(), "color".into(), "green".into()); + let response = command.eval_response(Resp3Frame::Number { + data: 3, + attributes: None, + }); + + assert_eq!(3, response.unwrap()); +} + +#[test] +fn test_eval_response_resp2_invalid_response() { + let command = HashSetCommand::new("my_hash".into(), "color".into(), "green".into()); + let response = command.eval_response(Resp2Frame::BulkString("3".into())); + + assert!(response.is_err()); +} + +#[test] +fn test_eval_response_resp3_invalid_response() { + let command = HashSetCommand::new("my_hash".into(), "color".into(), "green".into()); + let response = command.eval_response(Resp3Frame::BlobString { + data: "test".into(), + attributes: None, + }); + + assert!(response.is_err()); +} diff --git a/src/commands/tests/mod.rs b/src/commands/tests/mod.rs index 3bb5d61..1d3297a 100644 --- a/src/commands/tests/mod.rs +++ b/src/commands/tests/mod.rs @@ -3,6 +3,7 @@ mod bgsave; mod custom; mod get; pub(crate) mod hello; +mod hset; mod ping; mod publish; mod set; diff --git a/src/network/tests/client.rs b/src/network/tests/client.rs index 6ee25c6..f50de27 100644 --- a/src/network/tests/client.rs +++ b/src/network/tests/client.rs @@ -988,3 +988,69 @@ fn test_shorthand_bgsave_scheduled() { client.bgsave(true).unwrap().wait().unwrap(); } + +#[test] +fn test_shorthand_hset_str_argument() { + let clock = TestClock::new(vec![]); + + let mut network = NetworkMockBuilder::default() + .send( + 164, + "*4\r\n$4\r\nHSET\r\n$7\r\nmy_hash\r\n$5\r\ncolor\r\n$5\r\ngreen\r\n", + ) + .response(":1\r\n") + .into_mock(); + + let mut socket = SocketMock::new(164); + let client = create_mocked_client(&mut network, &mut socket, &clock, Resp2 {}); + + client.hset("my_hash", "color", "green").unwrap().wait().unwrap(); +} + +#[test] +fn test_shorthand_hset_string_argument() { + let clock = TestClock::new(vec![]); + + let mut network = NetworkMockBuilder::default() + .send( + 164, + "*4\r\n$4\r\nHSET\r\n$7\r\nmy_hash\r\n$5\r\ncolor\r\n$5\r\ngreen\r\n", + ) + .response(":1\r\n") + .into_mock(); + + let mut socket = SocketMock::new(164); + let client = create_mocked_client(&mut network, &mut socket, &clock, Resp2 {}); + + client + .hset("my_hash".to_string(), "color".to_string(), "green".to_string()) + .unwrap() + .wait() + .unwrap(); +} + +#[test] +fn test_shorthand_hset_bytes_argument() { + let clock = TestClock::new(vec![]); + + let mut network = NetworkMockBuilder::default() + .send( + 164, + "*4\r\n$4\r\nHSET\r\n$7\r\nmy_hash\r\n$5\r\ncolor\r\n$5\r\ngreen\r\n", + ) + .response(":1\r\n") + .into_mock(); + + let mut socket = SocketMock::new(164); + let client = create_mocked_client(&mut network, &mut socket, &clock, Resp2 {}); + + client + .hset( + Bytes::from_static(b"my_hash"), + Bytes::from_static(b"color"), + Bytes::from_static(b"green"), + ) + .unwrap() + .wait() + .unwrap(); +}
::FrameType: ToInteger, +
::FrameType: From, + { + self.send(HashSetCommand::new(key.into(), field.into(), value.into())) + } +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 4156123..27af5ad 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -5,10 +5,10 @@ pub mod custom; pub mod get; pub mod hello; pub mod helpers; +pub mod hset; pub mod ping; pub mod publish; pub mod set; - #[cfg(test)] pub(crate) mod tests; diff --git a/src/commands/tests/hset.rs b/src/commands/tests/hset.rs new file mode 100644 index 0000000..4cf2709 --- /dev/null +++ b/src/commands/tests/hset.rs @@ -0,0 +1,113 @@ +use crate::commands::hset::HashSetCommand; +use crate::commands::Command; +use redis_protocol::resp2::types::Frame as Resp2Frame; +use redis_protocol::resp3::types::Frame as Resp3Frame; + +#[test] +fn test_encode_single_field_resp2() { + let frame: Resp2Frame = HashSetCommand::new("my_hash".into(), "color".into(), "green".into()).encode(); + + assert!(frame.is_array()); + if let Resp2Frame::Array(array) = frame { + assert_eq!(4, array.len()); + assert_eq!("HSET", array[0].to_string().unwrap()); + assert_eq!("my_hash", array[1].to_string().unwrap()); + assert_eq!("color", array[2].to_string().unwrap()); + assert_eq!("green", array[3].to_string().unwrap()); + } +} + +#[test] +fn test_encode_single_field_resp3() { + let frame: Resp3Frame = HashSetCommand::new("my_hash".into(), "color".into(), "green".into()).encode(); + + if let Resp3Frame::Array { data, attributes: _ } = frame { + assert_eq!(4, data.len()); + assert_eq!("HSET", data[0].to_string().unwrap()); + assert_eq!("my_hash", data[1].to_string().unwrap()); + assert_eq!("color", data[2].to_string().unwrap()); + assert_eq!("green", data[3].to_string().unwrap()); + } +} + +#[test] +fn test_encode_multiple_fields_resp2() { + let frame: Resp2Frame = HashSetCommand::multiple( + "my_hash".into(), + [ + ("gender".into(), "male".into()), + ("material".into(), "wood".into()), + ], + ) + .encode(); + + if let Resp2Frame::Array(array) = frame { + assert_eq!(6, array.len()); + assert_eq!("HSET", array[0].to_string().unwrap()); + assert_eq!("my_hash", array[1].to_string().unwrap()); + assert_eq!("gender", array[2].to_string().unwrap()); + assert_eq!("male", array[3].to_string().unwrap()); + assert_eq!("material", array[4].to_string().unwrap()); + assert_eq!("wood", array[5].to_string().unwrap()); + } +} + +#[test] +fn test_encode_multiple_fields_resp3() { + let frame: Resp3Frame = HashSetCommand::multiple( + "my_hash".into(), + [ + ("gender".into(), "male".into()), + ("material".into(), "wood".into()), + ], + ) + .encode(); + + if let Resp3Frame::Array { data, attributes: _ } = frame { + assert_eq!(6, data.len()); + assert_eq!("HSET", data[0].to_string().unwrap()); + assert_eq!("my_hash", data[1].to_string().unwrap()); + assert_eq!("gender", data[2].to_string().unwrap()); + assert_eq!("male", data[3].to_string().unwrap()); + assert_eq!("material", data[4].to_string().unwrap()); + assert_eq!("wood", data[5].to_string().unwrap()); + } +} + +#[test] +fn test_eval_response_resp2_success() { + let command = HashSetCommand::new("my_hash".into(), "color".into(), "green".into()); + let response = command.eval_response(Resp2Frame::Integer(2)); + + assert_eq!(2, response.unwrap()); +} + +#[test] +fn test_eval_response_resp3_success() { + let command = HashSetCommand::new("my_hash".into(), "color".into(), "green".into()); + let response = command.eval_response(Resp3Frame::Number { + data: 3, + attributes: None, + }); + + assert_eq!(3, response.unwrap()); +} + +#[test] +fn test_eval_response_resp2_invalid_response() { + let command = HashSetCommand::new("my_hash".into(), "color".into(), "green".into()); + let response = command.eval_response(Resp2Frame::BulkString("3".into())); + + assert!(response.is_err()); +} + +#[test] +fn test_eval_response_resp3_invalid_response() { + let command = HashSetCommand::new("my_hash".into(), "color".into(), "green".into()); + let response = command.eval_response(Resp3Frame::BlobString { + data: "test".into(), + attributes: None, + }); + + assert!(response.is_err()); +} diff --git a/src/commands/tests/mod.rs b/src/commands/tests/mod.rs index 3bb5d61..1d3297a 100644 --- a/src/commands/tests/mod.rs +++ b/src/commands/tests/mod.rs @@ -3,6 +3,7 @@ mod bgsave; mod custom; mod get; pub(crate) mod hello; +mod hset; mod ping; mod publish; mod set; diff --git a/src/network/tests/client.rs b/src/network/tests/client.rs index 6ee25c6..f50de27 100644 --- a/src/network/tests/client.rs +++ b/src/network/tests/client.rs @@ -988,3 +988,69 @@ fn test_shorthand_bgsave_scheduled() { client.bgsave(true).unwrap().wait().unwrap(); } + +#[test] +fn test_shorthand_hset_str_argument() { + let clock = TestClock::new(vec![]); + + let mut network = NetworkMockBuilder::default() + .send( + 164, + "*4\r\n$4\r\nHSET\r\n$7\r\nmy_hash\r\n$5\r\ncolor\r\n$5\r\ngreen\r\n", + ) + .response(":1\r\n") + .into_mock(); + + let mut socket = SocketMock::new(164); + let client = create_mocked_client(&mut network, &mut socket, &clock, Resp2 {}); + + client.hset("my_hash", "color", "green").unwrap().wait().unwrap(); +} + +#[test] +fn test_shorthand_hset_string_argument() { + let clock = TestClock::new(vec![]); + + let mut network = NetworkMockBuilder::default() + .send( + 164, + "*4\r\n$4\r\nHSET\r\n$7\r\nmy_hash\r\n$5\r\ncolor\r\n$5\r\ngreen\r\n", + ) + .response(":1\r\n") + .into_mock(); + + let mut socket = SocketMock::new(164); + let client = create_mocked_client(&mut network, &mut socket, &clock, Resp2 {}); + + client + .hset("my_hash".to_string(), "color".to_string(), "green".to_string()) + .unwrap() + .wait() + .unwrap(); +} + +#[test] +fn test_shorthand_hset_bytes_argument() { + let clock = TestClock::new(vec![]); + + let mut network = NetworkMockBuilder::default() + .send( + 164, + "*4\r\n$4\r\nHSET\r\n$7\r\nmy_hash\r\n$5\r\ncolor\r\n$5\r\ngreen\r\n", + ) + .response(":1\r\n") + .into_mock(); + + let mut socket = SocketMock::new(164); + let client = create_mocked_client(&mut network, &mut socket, &clock, Resp2 {}); + + client + .hset( + Bytes::from_static(b"my_hash"), + Bytes::from_static(b"color"), + Bytes::from_static(b"green"), + ) + .unwrap() + .wait() + .unwrap(); +}