use crate::{
ErrorKind, RedisError, RedisErrorKind, Result,
client::BatchPreparedCommand,
commands::{
ClientKillOptions, ConnectionCommands, GenericCommands, ListCommands, StringCommands,
},
resp::cmd,
tests::{get_default_config, get_test_client, get_test_client_with_config},
};
use serial_test::serial;
#[tokio::test]
#[serial]
async fn unknown_command() -> Result<()> {
let client = get_test_client().await?;
let result = client.send::<()>(cmd("UNKNOWN").arg("arg"), None).await;
assert!(matches!(
result.unwrap_err().kind(),
ErrorKind::Redis(
redis @ RedisError {
kind: RedisErrorKind::Err,
..
},
) if redis.description().contains("unknown command 'UNKNOWN'")
));
Ok(())
}
#[tokio::test]
#[serial]
async fn reconnection() -> Result<()> {
let mut config = get_default_config()?;
config.connection_name = "regular".to_string();
let regular_client = get_test_client_with_config(config).await?;
let mut config = get_default_config()?;
config.connection_name = "killer".to_string();
let killer_client = get_test_client_with_config(config).await?;
let client_id = regular_client.client_id().await?;
killer_client
.client_kill(ClientKillOptions::default().id(client_id))
.await?;
let result = regular_client.set("key", "value").await;
assert!(result.is_err());
Ok(())
}
#[tokio::test]
#[serial]
async fn kill_on_write() -> Result<()> {
use crate::client::ReconnectionConfig;
let mut config = get_default_config()?;
config.reconnection = ReconnectionConfig::new_constant(0, 100);
let client = get_test_client_with_config(config).await?;
let result = client
.send::<()>(
cmd("SET")
.arg("key1")
.arg("value1")
.kill_connection_on_write(3),
Some(true),
)
.await;
assert!(result.is_ok());
let result = client
.send::<()>(
cmd("SET")
.arg("key2")
.arg("value2")
.kill_connection_on_write(2),
Some(true),
)
.await;
assert!(result.is_ok());
let result = client
.send::<()>(
cmd("SET")
.arg("key3")
.arg("value3")
.kill_connection_on_write(2),
Some(false),
)
.await;
assert!(result.is_err());
Ok(())
}
#[tokio::test]
#[serial]
async fn a_server_error_names_the_command_that_drew_it() -> Result<()> {
let client = get_test_client().await?;
client.del("a_list_key").await?;
client.lpush("a_list_key", "value").await?;
let result: Result<String> = client.get("a_list_key").await;
let error = result.expect_err("GET on a list must be refused by the server");
assert!(
matches!(error.kind(), ErrorKind::Redis(e) if e.kind == RedisErrorKind::WrongType),
"expected WRONGTYPE, got {error:?}"
);
assert_eq!(Some("GET"), error.command());
Ok(())
}
#[tokio::test]
#[serial]
async fn a_failing_command_inside_a_transaction_names_itself() -> Result<()> {
let client = get_test_client().await?;
client.del("a_list_for_tx").await?;
client.lpush("a_list_for_tx", "value").await?;
let mut transaction = client.create_transaction();
transaction.set("tx_ok_key", "value").forget();
transaction.get::<String>("a_list_for_tx").queue();
let result: Result<String> = transaction.execute().await;
let error = result.expect_err("GET on a list must be refused inside the transaction");
assert_eq!(
Some("GET"),
error.command(),
"the failing command must name itself, not the head of the batch: {error:?}"
);
Ok(())
}
#[tokio::test]
#[serial]
async fn a_single_awaited_command_in_a_pipeline_names_itself() -> Result<()> {
let client = get_test_client().await?;
let mut pipeline = client.create_pipeline();
pipeline.set("a_text_key", "not_a_number").forget();
pipeline.del("a_key_to_delete").forget();
pipeline.get::<i64>("a_text_key").queue();
let result: Result<i64> = pipeline.execute().await;
let error = result.expect_err("text read as an integer must be refused");
assert_eq!(
Some("GET"),
error.command(),
"the awaited command must name itself: {error:?}"
);
Ok(())
}