Skip to content

Commit 68570f7

Browse files
committed
added mocks for Context via ContextTrait and mockall crate
updated examples to include tests Signed-off-by: dmitrypol <dmitrypol@users.noreply.github.com>
1 parent 58601cd commit 68570f7

10 files changed

Lines changed: 481 additions & 18 deletions

File tree

Cargo.lock

Lines changed: 86 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,16 +173,18 @@ cfg-if = "1"
173173
valkey-module-macros-internals = { path = "valkeymodule-rs-macros-internals", version = "0.1.4"}
174174
log = "0.4"
175175
paste = "1.0.15"
176+
mockall = { version = "0.14.0", optional = true }
176177

177178
[dev-dependencies]
178179
anyhow = "1"
179180
redis = "0.28"
180181
lazy_static = "1"
181182
valkey-module-macros = { path = "valkeymodule-rs-macros", version = "0.1.4" }
182-
valkey-module = { path = "./", default-features = false, features = ["min-valkey-compatibility-version-8-0", "min-redis-compatibility-version-7-2"] }
183+
valkey-module = { path = "./", default-features = false, features = ["min-valkey-compatibility-version-8-0", "min-redis-compatibility-version-7-2", "test-mocks", "enable-system-alloc"] }
183184
cron = "0.15.0"
184185
chrono = "0.4.41"
185186
dashmap = "6.1.0"
187+
mockall = { version = "0.14.0"}
186188

187189
[build-dependencies]
188190
bindgen = "0.70"
@@ -199,3 +201,5 @@ min-redis-compatibility-version-6-0 = []
199201
enable-system-alloc = []
200202
# this is to indicate the Module wants to use RedisModule APIs for calls
201203
use-redismodule-api = []
204+
# exposes MockContext (generated via mockall) for downstream unit tests
205+
test-mocks = ["dep:mockall"]

examples/client.rs

Lines changed: 160 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
use valkey_module::alloc::ValkeyAlloc;
22
use valkey_module::{
3-
valkey_module, Context, NextArg, Status, ValkeyError, ValkeyResult, ValkeyString, ValkeyValue,
3+
valkey_module, ContextTrait, NextArg, Status, ValkeyError, ValkeyResult, ValkeyString,
4+
ValkeyValue,
45
};
56

6-
fn get_client_id(ctx: &Context, _args: Vec<ValkeyString>) -> ValkeyResult {
7+
fn get_client_id(ctx: &impl ContextTrait, _args: Vec<ValkeyString>) -> ValkeyResult {
78
let client_id = ctx.get_client_id();
89
Ok((client_id as i64).into())
910
}
1011

11-
fn get_client_name(ctx: &Context, _args: Vec<ValkeyString>) -> ValkeyResult {
12+
fn get_client_name(ctx: &impl ContextTrait, _args: Vec<ValkeyString>) -> ValkeyResult {
1213
// test for invalid client_id
1314
match ctx.get_client_name_by_id(0) {
1415
Ok(tmp) => ctx.log_notice(&format!(
@@ -21,7 +22,7 @@ fn get_client_name(ctx: &Context, _args: Vec<ValkeyString>) -> ValkeyResult {
2122
Ok(ValkeyValue::from(client_name.to_string()))
2223
}
2324

24-
fn get_client_username(ctx: &Context, _args: Vec<ValkeyString>) -> ValkeyResult {
25+
fn get_client_username(ctx: &impl ContextTrait, _args: Vec<ValkeyString>) -> ValkeyResult {
2526
// test for invalid client_id
2627
match ctx.get_client_username_by_id(0) {
2728
Ok(tmp) => ctx.log_notice(&format!(
@@ -34,7 +35,7 @@ fn get_client_username(ctx: &Context, _args: Vec<ValkeyString>) -> ValkeyResult
3435
Ok(ValkeyValue::from(client_username.to_string()))
3536
}
3637

37-
fn set_client_name(ctx: &Context, args: Vec<ValkeyString>) -> ValkeyResult {
38+
fn set_client_name(ctx: &impl ContextTrait, args: Vec<ValkeyString>) -> ValkeyResult {
3839
if args.len() != 2 {
3940
return Err(ValkeyError::WrongArity);
4041
}
@@ -47,7 +48,7 @@ fn set_client_name(ctx: &Context, args: Vec<ValkeyString>) -> ValkeyResult {
4748
Ok(ValkeyValue::Integer(resp2 as i64))
4849
}
4950

50-
fn get_client_cert(ctx: &Context, _args: Vec<ValkeyString>) -> ValkeyResult {
51+
fn get_client_cert(ctx: &impl ContextTrait, _args: Vec<ValkeyString>) -> ValkeyResult {
5152
// unless connection is made with cert, this will return Err, so just log it and return nothing
5253
match ctx.get_client_cert() {
5354
Ok(tmp) => ctx.log_notice(&format!("client_cert: {:?}", tmp.to_string())),
@@ -56,7 +57,7 @@ fn get_client_cert(ctx: &Context, _args: Vec<ValkeyString>) -> ValkeyResult {
5657
Ok("".into())
5758
}
5859

59-
fn get_client_info(ctx: &Context, _args: Vec<ValkeyString>) -> ValkeyResult {
60+
fn get_client_info(ctx: &impl ContextTrait, _args: Vec<ValkeyString>) -> ValkeyResult {
6061
// test for invalid client_id
6162
let client_info_by_id = ctx.get_client_info_by_id(0);
6263
ctx.log_notice(&format!(
@@ -69,7 +70,7 @@ fn get_client_info(ctx: &Context, _args: Vec<ValkeyString>) -> ValkeyResult {
6970
Ok(ValkeyValue::from(client_info.version.to_string()))
7071
}
7172

72-
fn get_client_ip(ctx: &Context, _args: Vec<ValkeyString>) -> ValkeyResult {
73+
fn get_client_ip(ctx: &impl ContextTrait, _args: Vec<ValkeyString>) -> ValkeyResult {
7374
// test for invalid client_id
7475
let client_ip_by_id = ctx.get_client_ip_by_id(0);
7576
ctx.log_notice(&format!(
@@ -79,7 +80,7 @@ fn get_client_ip(ctx: &Context, _args: Vec<ValkeyString>) -> ValkeyResult {
7980
Ok(ctx.get_client_ip()?.into())
8081
}
8182

82-
fn deauth_client_by_id(ctx: &Context, args: Vec<ValkeyString>) -> ValkeyResult {
83+
fn deauth_client_by_id(ctx: &impl ContextTrait, args: Vec<ValkeyString>) -> ValkeyResult {
8384
if args.len() != 2 {
8485
return Err(ValkeyError::WrongArity);
8586
}
@@ -95,7 +96,7 @@ fn deauth_client_by_id(ctx: &Context, args: Vec<ValkeyString>) -> ValkeyResult {
9596
}
9697
}
9798

98-
fn config_get(ctx: &Context, args: Vec<ValkeyString>) -> ValkeyResult {
99+
fn config_get(ctx: &impl ContextTrait, args: Vec<ValkeyString>) -> ValkeyResult {
99100
if args.len() != 2 {
100101
return Err(ValkeyError::WrongArity);
101102
}
@@ -125,3 +126,152 @@ valkey_module! {
125126
["client.config_get", config_get, "", 0, 0, 0]
126127
]
127128
}
129+
130+
#[cfg(test)]
131+
mod tests {
132+
use super::*;
133+
use std::ptr::null_mut;
134+
use valkey_module::{MockContext, RedisModuleClientInfo};
135+
136+
fn null_valkey_string() -> ValkeyString {
137+
ValkeyString::from_redis_module_string(null_mut(), null_mut())
138+
}
139+
140+
fn module_client_info(id: u64) -> RedisModuleClientInfo {
141+
RedisModuleClientInfo {
142+
version: 1,
143+
flags: 0,
144+
id,
145+
addr: [0; 46],
146+
port: 6379,
147+
db: 0,
148+
}
149+
}
150+
151+
#[test]
152+
fn test_get_client_id() {
153+
let mut ctx = MockContext::new();
154+
ctx.expect_get_client_id().times(1).returning(|| 42);
155+
let reply = get_client_id(&ctx, vec![]).unwrap();
156+
assert_eq!(reply, ValkeyValue::Integer(42));
157+
}
158+
159+
#[test]
160+
fn test_get_client_name() {
161+
let mut ctx = MockContext::new();
162+
ctx.expect_log_notice().times(1).return_const(());
163+
ctx.expect_get_client_name_by_id()
164+
.times(1)
165+
.returning(|_| Err(ValkeyError::Str("no such client")));
166+
ctx.expect_get_client_name()
167+
.times(1)
168+
.returning(|| Err(ValkeyError::Str("no name")));
169+
170+
let err = get_client_name(&ctx, vec![]).unwrap_err();
171+
assert!(matches!(err, ValkeyError::Str("no name")));
172+
}
173+
174+
#[test]
175+
fn test_get_client_username() {
176+
let mut ctx = MockContext::new();
177+
ctx.expect_log_notice().times(1).return_const(());
178+
ctx.expect_get_client_username_by_id()
179+
.times(1)
180+
.returning(|_| Err(ValkeyError::Str("no such client")));
181+
ctx.expect_get_client_username()
182+
.times(1)
183+
.returning(|| Err(ValkeyError::Str("no user")));
184+
185+
let err = get_client_username(&ctx, vec![]).unwrap_err();
186+
assert!(matches!(err, ValkeyError::Str("no user")));
187+
}
188+
189+
#[test]
190+
fn test_set_client_name_wrong_arity() {
191+
let ctx = MockContext::new();
192+
let err = set_client_name(&ctx, vec![null_valkey_string()]).unwrap_err();
193+
assert!(matches!(err, ValkeyError::WrongArity));
194+
}
195+
196+
#[test]
197+
fn test_set_client_name() {
198+
let mut ctx = MockContext::new();
199+
ctx.expect_log_notice().times(1).return_const(());
200+
ctx.expect_set_client_name_by_id()
201+
.times(1)
202+
.returning(|_, _| Status::Ok);
203+
ctx.expect_set_client_name()
204+
.times(1)
205+
.returning(|_| Status::Ok);
206+
207+
let reply =
208+
set_client_name(&ctx, vec![null_valkey_string(), null_valkey_string()]).unwrap();
209+
assert_eq!(reply, ValkeyValue::Integer(Status::Ok as i64));
210+
}
211+
212+
#[test]
213+
fn test_get_client_cert() {
214+
let mut ctx = MockContext::new();
215+
ctx.expect_log_notice().times(1).return_const(());
216+
ctx.expect_get_client_cert()
217+
.times(1)
218+
.returning(|| Err(ValkeyError::Str("no cert")));
219+
let reply = get_client_cert(&ctx, vec![]).unwrap();
220+
assert_eq!(reply, ValkeyValue::BulkString(String::new()));
221+
}
222+
223+
#[test]
224+
fn test_get_client_info() {
225+
let mut ctx = MockContext::new();
226+
ctx.expect_log_notice().times(2).return_const(());
227+
ctx.expect_get_client_info_by_id()
228+
.times(1)
229+
.returning(|_| Ok(module_client_info(0)));
230+
ctx.expect_get_client_info()
231+
.times(1)
232+
.returning(|| Ok(module_client_info(42)));
233+
234+
let reply = get_client_info(&ctx, vec![]).unwrap();
235+
assert_eq!(reply, ValkeyValue::BulkString("1".to_string()));
236+
}
237+
238+
#[test]
239+
fn test_get_client_ip() {
240+
let mut ctx = MockContext::new();
241+
ctx.expect_get_client_ip_by_id()
242+
.times(1)
243+
.returning(|_| Ok("0.0.0.0".to_string()));
244+
ctx.expect_log_notice().times(1).return_const(());
245+
ctx.expect_get_client_ip()
246+
.times(1)
247+
.returning(|| Ok("127.0.0.1".to_string()));
248+
let reply = get_client_ip(&ctx, vec![]).unwrap();
249+
assert_eq!(reply, ValkeyValue::BulkString("127.0.0.1".to_string()));
250+
}
251+
252+
#[test]
253+
fn test_deauth_client_by_id_wrong_arity() {
254+
let ctx = MockContext::new();
255+
let err = deauth_client_by_id(&ctx, vec![null_valkey_string()]).unwrap_err();
256+
assert!(matches!(err, ValkeyError::WrongArity));
257+
}
258+
259+
#[test]
260+
#[ignore]
261+
fn test_death_client_by_id() {
262+
// TODO - test when ValkeyString is unit testable
263+
}
264+
265+
#[test]
266+
fn test_config_get_wrong_arity() {
267+
let ctx = MockContext::new();
268+
let err = config_get(&ctx, vec![null_valkey_string()]).unwrap_err();
269+
assert!(matches!(err, ValkeyError::WrongArity));
270+
}
271+
272+
#[test]
273+
#[ignore]
274+
fn test_config_get() {
275+
// TODO - test when ValkeyString is unit testable
276+
}
277+
}

0 commit comments

Comments
 (0)