#![allow(clippy::needless_raw_string_hashes)]
#![allow(clippy::uninlined_format_args)]
use redis_oxide::{Client, ConnectionConfig, Script, ScriptManager};
fn redis_url() -> String {
std::env::var("REDIS_URL").unwrap_or_else(|_| "redis://localhost:6379".to_string())
}
async fn setup_client() -> Result<Client, redis_oxide::RedisError> {
let config = ConnectionConfig::new(redis_url().as_str());
Client::connect(config).await
}
#[tokio::test]
async fn test_basic_script_execution() -> Result<(), Box<dyn std::error::Error>> {
let client = setup_client().await?;
let script = "return 'Hello, World!'";
let result: String = client.eval(script, vec![], vec![]).await?;
assert_eq!(result, "Hello, World!");
let script = "return redis.call('GET', KEYS[1])";
client.set("test_key", "test_value").await?;
let result: Option<String> = client
.eval(script, vec!["test_key".to_string()], vec![])
.await?;
assert_eq!(result, Some("test_value".to_string()));
let script = "return ARGV[1] .. ':' .. ARGV[2]";
let result: String = client
.eval(
script,
vec![],
vec!["hello".to_string(), "world".to_string()],
)
.await?;
assert_eq!(result, "hello:world");
Ok(())
}
#[tokio::test]
async fn test_script_with_redis_operations() -> Result<(), Box<dyn std::error::Error>> {
let client = setup_client().await?;
let script = r#"
redis.call('SET', KEYS[1], ARGV[1])
return redis.call('GET', KEYS[1])
"#;
let result: String = client
.eval(
script,
vec!["script_key".to_string()],
vec!["script_value".to_string()],
)
.await?;
assert_eq!(result, "script_value");
let value: Option<String> = client.get("script_key").await?;
assert_eq!(value, Some("script_value".to_string()));
Ok(())
}
#[tokio::test]
async fn test_script_load_and_evalsha() -> Result<(), Box<dyn std::error::Error>> {
let client = setup_client().await?;
let script = "return KEYS[1] .. ':' .. ARGV[1]";
let sha = client.script_load(script).await?;
assert_eq!(sha.len(), 40);
let result: String = client
.evalsha(&sha, vec!["test".to_string()], vec!["value".to_string()])
.await?;
assert_eq!(result, "test:value");
let exists = client.script_exists(vec![sha.clone()]).await?;
assert_eq!(exists, vec![true]);
let fake_sha = "0000000000000000000000000000000000000000";
let exists_fake = client.script_exists(vec![fake_sha.to_string()]).await?;
assert_eq!(exists_fake, vec![false]);
Ok(())
}
#[tokio::test]
async fn test_script_struct() -> Result<(), Box<dyn std::error::Error>> {
let client = setup_client().await?;
let script = Script::new("return KEYS[1] .. ':' .. ARGV[1]");
assert!(!script.sha().is_empty());
assert_eq!(script.source(), "return KEYS[1] .. ':' .. ARGV[1]");
let result: String = script
.execute(
&client,
vec!["key1".to_string()],
vec!["value1".to_string()],
)
.await?;
assert_eq!(result, "key1:value1");
let sha = script.load(&client).await?;
assert_eq!(sha, script.sha());
let result2: String = script
.execute(
&client,
vec!["key2".to_string()],
vec!["value2".to_string()],
)
.await?;
assert_eq!(result2, "key2:value2");
Ok(())
}
#[tokio::test]
async fn test_script_manager() -> Result<(), Box<dyn std::error::Error>> {
let client = setup_client().await?;
let manager = ScriptManager::new();
let script1 = Script::new("return 'script1:' .. ARGV[1]");
let script2 = Script::new("return 'script2:' .. ARGV[1]");
manager.register("test_script1", script1).await;
manager.register("test_script2", script2).await;
assert_eq!(manager.len().await, 2);
assert!(!manager.is_empty().await);
let result1: String = manager
.execute("test_script1", &client, vec![], vec!["hello".to_string()])
.await?;
assert_eq!(result1, "script1:hello");
let result2: String = manager
.execute("test_script2", &client, vec![], vec!["world".to_string()])
.await?;
assert_eq!(result2, "script2:world");
let loaded = manager.load_all(&client).await?;
assert_eq!(loaded.len(), 2);
assert!(loaded.contains_key("test_script1"));
assert!(loaded.contains_key("test_script2"));
let script_names = manager.list_scripts().await;
assert_eq!(script_names.len(), 2);
assert!(script_names.contains(&"test_script1".to_string()));
assert!(script_names.contains(&"test_script2".to_string()));
Ok(())
}
#[tokio::test]
async fn test_atomic_increment_script() -> Result<(), Box<dyn std::error::Error>> {
let client = setup_client().await?;
let script = r#"
local key = KEYS[1]
local increment = tonumber(ARGV[1])
local expiration = tonumber(ARGV[2])
local current = redis.call('GET', key) or 0
local new_value = tonumber(current) + increment
redis.call('SET', key, new_value)
redis.call('EXPIRE', key, expiration)
return new_value
"#;
let result1: i64 = client
.eval(
script,
vec!["counter".to_string()],
vec!["5".to_string(), "60".to_string()],
)
.await?;
assert_eq!(result1, 5);
let result2: i64 = client
.eval(
script,
vec!["counter".to_string()],
vec!["3".to_string(), "60".to_string()],
)
.await?;
assert_eq!(result2, 8);
let value: Option<String> = client.get("counter").await?;
assert_eq!(value, Some("8".to_string()));
let ttl: Option<i64> = client.ttl("counter").await?;
assert!(ttl.is_some());
assert!(ttl.unwrap() > 0);
Ok(())
}
#[tokio::test]
async fn test_conditional_set_script() -> Result<(), Box<dyn std::error::Error>> {
let client = setup_client().await?;
let script = r#"
local key = KEYS[1]
local expected = ARGV[1]
local new_value = ARGV[2]
local current = redis.call('GET', key)
if current == expected then
redis.call('SET', key, new_value)
return 1
else
return 0
end
"#;
client.set("conditional_key", "initial").await?;
let result1: i64 = client
.eval(
script,
vec!["conditional_key".to_string()],
vec!["initial".to_string(), "updated".to_string()],
)
.await?;
assert_eq!(result1, 1);
let value: Option<String> = client.get("conditional_key").await?;
assert_eq!(value, Some("updated".to_string()));
let result2: i64 = client
.eval(
script,
vec!["conditional_key".to_string()],
vec!["wrong".to_string(), "failed".to_string()],
)
.await?;
assert_eq!(result2, 0);
let value2: Option<String> = client.get("conditional_key").await?;
assert_eq!(value2, Some("updated".to_string()));
Ok(())
}
#[tokio::test]
async fn test_script_with_multiple_keys() -> Result<(), Box<dyn std::error::Error>> {
let client = setup_client().await?;
let script = r#"
local key1 = KEYS[1]
local key2 = KEYS[2]
local value = ARGV[1]
redis.call('SET', key1, value)
redis.call('SET', key2, value)
return redis.call('MGET', key1, key2)
"#;
let result: Vec<String> = client
.eval(
script,
vec!["multi_key1".to_string(), "multi_key2".to_string()],
vec!["shared_value".to_string()],
)
.await?;
assert_eq!(result, vec!["shared_value", "shared_value"]);
let value1: Option<String> = client.get("multi_key1").await?;
let value2: Option<String> = client.get("multi_key2").await?;
assert_eq!(value1, Some("shared_value".to_string()));
assert_eq!(value2, Some("shared_value".to_string()));
Ok(())
}
#[tokio::test]
async fn test_script_flush() -> Result<(), Box<dyn std::error::Error>> {
let client = setup_client().await?;
let script = "return 'test'";
let sha = client.script_load(script).await?;
let exists_before = client.script_exists(vec![sha.clone()]).await?;
assert_eq!(exists_before, vec![true]);
client.script_flush().await?;
let exists_after = client.script_exists(vec![sha]).await?;
assert_eq!(exists_after, vec![false]);
Ok(())
}
#[tokio::test]
async fn test_script_error_handling() -> Result<(), Box<dyn std::error::Error>> {
let client = setup_client().await?;
let fake_sha = "0000000000000000000000000000000000000000";
let result = client
.evalsha::<String>(fake_sha, vec!["key".to_string()], vec!["value".to_string()])
.await;
assert!(result.is_err());
let bad_script = "return invalid lua syntax !!!";
let result = client.eval::<String>(bad_script, vec![], vec![]).await;
assert!(result.is_err());
Ok(())
}
#[tokio::test]
async fn test_script_pattern_examples() -> Result<(), Box<dyn std::error::Error>> {
let client = setup_client().await?;
let rate_limit_script = r#"
local key = KEYS[1]
local window = tonumber(ARGV[1])
local limit = tonumber(ARGV[2])
local now = redis.call('TIME')[1]
-- Remove old entries
redis.call('ZREMRANGEBYSCORE', key, 0, now - window)
-- Count current entries
local current = redis.call('ZCARD', key)
if current < limit then
-- Add current request
redis.call('ZADD', key, now, now)
redis.call('EXPIRE', key, window)
return {1, limit - current - 1}
else
return {0, 0}
end
"#;
let result: Vec<i64> = client
.eval(
rate_limit_script,
vec!["rate_limit:user1".to_string()],
vec!["60".to_string(), "5".to_string()], )
.await?;
assert_eq!(result[0], 1); assert_eq!(result[1], 4);
Ok(())
}