use ferriskey::Value;
const TEST_LIB: &str = "#!lua name=ferriskey_test\n\
redis.register_function('ft_echo', function(keys, args) return args[1] end)\n\
redis.register_function{function_name='ft_read', callback=function(keys, args) return redis.call('GET', keys[1]) end, flags={'no-writes'}}\n\
";
async fn connect() -> ferriskey::Client {
ferriskey::connect("redis://127.0.0.1:6379")
.await
.expect("failed to connect to local Valkey")
}
#[tokio::test]
#[serial_test::serial]
async fn test_function_load_and_fcall_echo() {
let client = connect().await;
let name: String = client
.function_load_replace(TEST_LIB)
.await
.expect("FUNCTION LOAD failed");
assert_eq!(name, "ferriskey_test");
let result: String = client
.fcall("ft_echo", &["{test}:k1"], &["hello_world"])
.await
.expect("FCALL failed");
assert_eq!(result, "hello_world");
}
#[tokio::test]
#[serial_test::serial]
async fn test_function_load_replace_overwrites() {
let client = connect().await;
let name1: String = client
.function_load_replace(TEST_LIB)
.await
.expect("first FUNCTION LOAD failed");
assert_eq!(name1, "ferriskey_test");
let name2: String = client
.function_load_replace(TEST_LIB)
.await
.expect("second FUNCTION LOAD (replace) failed");
assert_eq!(name2, "ferriskey_test");
}
#[tokio::test]
#[serial_test::serial]
async fn test_function_list_finds_library() {
let client = connect().await;
let _: String = client
.function_load_replace(TEST_LIB)
.await
.expect("FUNCTION LOAD failed");
let val = client
.function_list("ferriskey_test")
.await
.expect("FUNCTION LIST failed");
match val {
Value::Array(ref entries) => {
assert!(
!entries.is_empty(),
"FUNCTION LIST should return at least one library, got empty array"
);
}
Value::Map(ref entries) => {
assert!(
!entries.is_empty(),
"FUNCTION LIST should return at least one library, got empty map"
);
}
other => panic!(
"Expected Array or Map from FUNCTION LIST, got: {:?}",
other
),
}
}
#[tokio::test]
#[serial_test::serial]
async fn test_function_delete_removes_library() {
let client = connect().await;
let _: String = client
.function_load_replace(TEST_LIB)
.await
.expect("FUNCTION LOAD failed");
client
.function_delete("ferriskey_test")
.await
.expect("FUNCTION DELETE failed");
let result: ferriskey::Result<String> =
client.fcall("ft_echo", &["{test}:k1"], &["x"]).await;
assert!(result.is_err(), "FCALL should fail after FUNCTION DELETE");
let _: String = client
.function_load_replace(TEST_LIB)
.await
.expect("re-load failed");
}
#[tokio::test]
#[serial_test::serial]
async fn test_fcall_readonly() {
let client = connect().await;
let _: String = client
.function_load_replace(TEST_LIB)
.await
.expect("FUNCTION LOAD failed");
client
.set("{test}:readonly_k", "readonly_val")
.await
.expect("SET failed");
let result: String = client
.fcall_readonly("ft_read", &["{test}:readonly_k"], &[] as &[&str])
.await
.expect("FCALL_RO failed");
assert_eq!(result, "readonly_val");
client.del(&["{test}:readonly_k"]).await.ok();
}
#[tokio::test]
#[serial_test::serial]
async fn test_fcall_no_keys() {
let client = connect().await;
let _: String = client
.function_load_replace(TEST_LIB)
.await
.expect("FUNCTION LOAD failed");
let result: String = client
.fcall("ft_echo", &[] as &[&str], &["no_key_test"])
.await
.expect("FCALL with 0 keys failed");
assert_eq!(result, "no_key_test");
}
#[tokio::test]
#[serial_test::serial]
async fn test_pipeline_fcall() {
let client = connect().await;
let _: String = client
.function_load_replace(TEST_LIB)
.await
.expect("FUNCTION LOAD failed");
let mut pipe = client.pipeline();
let slot1 = pipe.fcall::<String>("ft_echo", &["{test}:p1"], &["pipe_a"]);
let slot2 = pipe.fcall::<String>("ft_echo", &["{test}:p1"], &["pipe_b"]);
pipe.execute().await.expect("pipeline execute failed");
assert_eq!(slot1.value().expect("slot1"), "pipe_a");
assert_eq!(slot2.value().expect("slot2"), "pipe_b");
}