Skip to main content

redis_test/
lib.rs

1//! Testing support
2//!
3//! This crate provides utilities for testing Redis clients and applications:
4//!
5//! * **Mock Connections**: `MockRedisConnection` implements `ConnectionLike` and can be
6//!   used in the same place as any other type that behaves like a Redis connection. This is useful
7//!   for writing unit tests without needing a real Redis server.
8//! * **Standalone Servers**: [`server::RedisServer`] provides an easy way to spin up
9//!   and manage a real local Redis instance for integration tests.
10//! * **Clusters**: [`cluster::RedisCluster`] provides functionality to spawn and configure
11//!   a local Redis cluster with multiple nodes and replicas.
12//! * **Sentinels**: [`sentinel::RedisSentinelCluster`] provides functionality to spawn
13//!   and configure a local Redis sentinel cluster.
14//!
15//! # Example (Mock Connection)
16//!
17//! ```rust
18//! use redis::{ConnectionLike, RedisError};
19//! use redis_test::{MockCmd, MockRedisConnection};
20//!
21//! fn my_exists<C: ConnectionLike>(conn: &mut C, key: &str) -> Result<bool, RedisError> {
22//!     let exists: bool = redis::cmd("EXISTS").arg(key).query(conn)?;
23//!     Ok(exists)
24//! }
25//!
26//! let mut mock_connection = MockRedisConnection::new(vec![
27//!     MockCmd::new(redis::cmd("EXISTS").arg("foo"), Ok("1")),
28//! ]);
29//!
30//! let result = my_exists(&mut mock_connection, "foo").unwrap();
31//! assert_eq!(result, true);
32//! ```
33
34pub mod cluster;
35pub mod sentinel;
36pub mod server;
37pub mod utils;
38
39use std::collections::{HashMap, HashSet, VecDeque};
40use std::sync::{Arc, Mutex};
41
42use redis::{
43    Cmd, ConnectionLike, ErrorKind, Pipeline, RedisError, RedisResult, ServerError, Value,
44};
45
46#[cfg(feature = "aio")]
47use futures::{FutureExt, future};
48
49#[cfg(feature = "aio")]
50use redis::{RedisFuture, aio::ConnectionLike as AioConnectionLike};
51
52/// Helper trait for converting test values into a `redis::Value` returned from a
53/// `MockRedisConnection`. This is necessary because neither `redis::types::ToRedisArgs`
54/// nor `redis::types::FromRedisValue` performs the precise conversion needed.
55pub trait IntoRedisValue {
56    /// Convert a value into `redis::Value`.
57    fn into_redis_value(self) -> Value;
58}
59
60macro_rules! into_redis_value_impl_int {
61    ($t:ty) => {
62        impl IntoRedisValue for $t {
63            fn into_redis_value(self) -> Value {
64                Value::Int(self as i64)
65            }
66        }
67    };
68}
69
70into_redis_value_impl_int!(i8);
71into_redis_value_impl_int!(i16);
72into_redis_value_impl_int!(i32);
73into_redis_value_impl_int!(i64);
74into_redis_value_impl_int!(u8);
75into_redis_value_impl_int!(u16);
76into_redis_value_impl_int!(u32);
77
78macro_rules! into_redis_value_impl_float {
79    ($t:ty) => {
80        impl IntoRedisValue for $t {
81            fn into_redis_value(self) -> Value {
82                Value::Double(self as f64)
83            }
84        }
85    };
86}
87
88into_redis_value_impl_float!(f32);
89into_redis_value_impl_float!(f64);
90
91impl IntoRedisValue for String {
92    fn into_redis_value(self) -> Value {
93        Value::BulkString(self.as_bytes().to_vec())
94    }
95}
96
97impl IntoRedisValue for &str {
98    fn into_redis_value(self) -> Value {
99        Value::BulkString(self.as_bytes().to_vec())
100    }
101}
102
103impl IntoRedisValue for bool {
104    fn into_redis_value(self) -> Value {
105        Value::Boolean(self)
106    }
107}
108
109#[cfg(feature = "bytes")]
110impl IntoRedisValue for bytes::Bytes {
111    fn into_redis_value(self) -> Value {
112        Value::BulkString(self.to_vec())
113    }
114}
115
116impl IntoRedisValue for Vec<u8> {
117    fn into_redis_value(self) -> Value {
118        Value::BulkString(self)
119    }
120}
121
122impl IntoRedisValue for Vec<Value> {
123    fn into_redis_value(self) -> Value {
124        Value::Array(self)
125    }
126}
127
128impl IntoRedisValue for Vec<(Value, Value)> {
129    fn into_redis_value(self) -> Value {
130        Value::Map(self)
131    }
132}
133
134impl<K, V> IntoRedisValue for HashMap<K, V>
135where
136    K: IntoRedisValue,
137    V: IntoRedisValue,
138{
139    fn into_redis_value(self) -> Value {
140        Value::Map(
141            self.into_iter()
142                .map(|(k, v)| (k.into_redis_value(), v.into_redis_value()))
143                .collect(),
144        )
145    }
146}
147
148impl<V> IntoRedisValue for HashSet<V>
149where
150    V: IntoRedisValue,
151{
152    fn into_redis_value(self) -> Value {
153        Value::Set(
154            self.into_iter()
155                .map(IntoRedisValue::into_redis_value)
156                .collect(),
157        )
158    }
159}
160
161impl IntoRedisValue for Value {
162    fn into_redis_value(self) -> Value {
163        self
164    }
165}
166
167impl IntoRedisValue for ServerError {
168    fn into_redis_value(self) -> Value {
169        Value::ServerError(self)
170    }
171}
172
173/// Build [`Value`]s from a JSON-like notation
174///
175/// This macro handles:
176///
177/// * `u8`, `u16`, `u32`, `i8`, `i16`, `i32`, `i64`, `String`, `&str` and other types that implement [`IntoRedisValue`]
178/// * `true`, `false` - map to their corresponding [`Value::Boolean`]
179/// * `nil` - maps to [`Value::Nil`]
180/// * `ok`, `okay` - map to [`Value::Okay`]
181/// * `[element1, element2, ..., elementN]` - maps to [`Value::Array`]
182/// * `{key1: value1, key2: value2, ..., keyN: valueN]` - maps to [`Value::Map`]
183/// * `set:[element1, element2, ..., elementN]` - maps to [`Value::Set`] (wrap in `()` within complex structures)
184/// * `simple:"SomeString"` - maps to [`Value::SimpleString`] (wrap in `()` within complex structures)
185///
186/// # Example
187///
188/// ```rust
189/// use redis::Value;
190/// use redis_test::redis_value;
191///
192/// let actual = redis_value!([42, "foo", {true: nil}]);
193///
194/// let expected = Value::Array(vec![
195///   Value::Int(42),
196///   Value::BulkString("foo".as_bytes().to_vec()),
197///   Value::Map(vec![(Value::Boolean(true), Value::Nil)])
198/// ]);
199/// assert_eq!(actual, expected)
200/// ```
201#[macro_export]
202macro_rules! redis_value {
203    // Map of elements
204    ({$($k:tt: $v:tt),* $(,)*}) => {
205        redis::Value::Map(vec![$(($crate::redis_value!($k), $crate::redis_value!($v))),*])
206    };
207
208    // Array of elements
209    ([$($e:tt),* $(,)*]) => {
210        redis::Value::Array(vec![$($crate::redis_value!($e)),*])
211    };
212
213    // Set of elements
214    (set:[$($e:tt),* $(,)*]) => {
215        redis::Value::Set(vec![$($crate::redis_value!($e)),*])
216    };
217
218    // Simple strings
219    (simple:$e:tt) => {
220        redis::Value::SimpleString($e.to_string())
221    };
222
223    // Nil
224    (nil) => {
225        redis::Value::Nil
226    };
227
228    // Okay
229    (ok) => {
230        $crate::redis_value!(okay)
231    };
232
233    (okay) => {
234        redis::Value::Okay
235    };
236
237    // Unwrap extra context-isolating parentheses
238    (($context:tt:$e:tt)) => {
239        $crate::redis_value!($context:$e)
240    };
241
242    // Fallback to primitive conversion
243    ($e:expr) => {
244        $crate::IntoRedisValue::into_redis_value($e)
245    };
246}
247
248/// Helper trait for converting `redis::Cmd` and `redis::Pipeline` instances into
249/// encoded byte vectors.
250pub trait IntoRedisCmdBytes {
251    /// Convert a command into an encoded byte vector.
252    fn into_redis_cmd_bytes(self) -> Vec<u8>;
253}
254
255impl IntoRedisCmdBytes for Cmd {
256    fn into_redis_cmd_bytes(self) -> Vec<u8> {
257        self.get_packed_command()
258    }
259}
260
261impl IntoRedisCmdBytes for &Cmd {
262    fn into_redis_cmd_bytes(self) -> Vec<u8> {
263        self.get_packed_command()
264    }
265}
266
267impl IntoRedisCmdBytes for &mut Cmd {
268    fn into_redis_cmd_bytes(self) -> Vec<u8> {
269        self.get_packed_command()
270    }
271}
272
273impl IntoRedisCmdBytes for Pipeline {
274    fn into_redis_cmd_bytes(self) -> Vec<u8> {
275        self.get_packed_pipeline()
276    }
277}
278
279impl IntoRedisCmdBytes for &Pipeline {
280    fn into_redis_cmd_bytes(self) -> Vec<u8> {
281        self.get_packed_pipeline()
282    }
283}
284
285impl IntoRedisCmdBytes for &mut Pipeline {
286    fn into_redis_cmd_bytes(self) -> Vec<u8> {
287        self.get_packed_pipeline()
288    }
289}
290
291/// Represents a command to be executed against a `MockConnection`.
292#[derive(Clone)]
293pub struct MockCmd {
294    cmd_bytes: Vec<u8>,
295    responses: Result<Vec<Value>, RedisError>,
296}
297
298impl MockCmd {
299    /// Create a new `MockCmd` given a Redis command and either a value convertible to
300    /// a `redis::Value` or a `RedisError`.
301    pub fn new<C, V>(cmd: C, response: Result<V, RedisError>) -> Self
302    where
303        C: IntoRedisCmdBytes,
304        V: IntoRedisValue,
305    {
306        MockCmd {
307            cmd_bytes: cmd.into_redis_cmd_bytes(),
308            responses: response.map(|r| vec![r.into_redis_value()]),
309        }
310    }
311
312    /// Create a new `MockCommand` given a Redis command/pipeline and a vector of value convertible
313    /// to a `redis::Value` or a `RedisError`.
314    pub fn with_values<C, V>(cmd: C, responses: Result<Vec<V>, RedisError>) -> Self
315    where
316        C: IntoRedisCmdBytes,
317        V: IntoRedisValue,
318    {
319        MockCmd {
320            cmd_bytes: cmd.into_redis_cmd_bytes(),
321            responses: responses.map(|xs| xs.into_iter().map(|x| x.into_redis_value()).collect()),
322        }
323    }
324}
325
326/// A mock Redis client for testing without a server. `MockRedisConnection` checks whether the
327/// client submits a specific sequence of commands and generates an error if it does not.
328#[derive(Clone)]
329pub struct MockRedisConnection {
330    commands: Arc<Mutex<VecDeque<MockCmd>>>,
331    assert_is_empty_on_drop: bool,
332}
333
334impl MockRedisConnection {
335    /// Construct a new from the given sequence of commands.
336    pub fn new<I>(commands: I) -> Self
337    where
338        I: IntoIterator<Item = MockCmd>,
339    {
340        MockRedisConnection {
341            commands: Arc::new(Mutex::new(VecDeque::from_iter(commands))),
342            assert_is_empty_on_drop: false,
343        }
344    }
345
346    /// Enable assertion to ensure all commands have been consumed
347    pub fn assert_all_commands_consumed(mut self) -> Self {
348        self.assert_is_empty_on_drop = true;
349        self
350    }
351}
352
353impl Drop for MockRedisConnection {
354    fn drop(&mut self) {
355        if self.assert_is_empty_on_drop {
356            let commands = self.commands.lock().unwrap();
357            if Arc::strong_count(&self.commands) == 1 {
358                assert!(commands.back().is_none());
359            }
360        }
361    }
362}
363
364impl MockRedisConnection {
365    pub fn is_empty(&self) -> bool {
366        self.commands.lock().unwrap().is_empty()
367    }
368}
369
370impl ConnectionLike for MockRedisConnection {
371    fn req_packed_command(&mut self, cmd: &[u8]) -> RedisResult<Value> {
372        let mut commands = self.commands.lock().unwrap();
373        let next_cmd = commands.pop_front().ok_or_else(|| {
374            self.assert_is_empty_on_drop = false;
375            RedisError::from((ErrorKind::Client, "TEST", "unexpected command".to_owned()))
376        })?;
377
378        if cmd != next_cmd.cmd_bytes {
379            self.assert_is_empty_on_drop = false;
380            return Err(RedisError::from((
381                ErrorKind::Client,
382                "TEST",
383                format!(
384                    "unexpected command: expected={}, actual={}",
385                    String::from_utf8(next_cmd.cmd_bytes)
386                        .unwrap_or_else(|_| "decode error".to_owned()),
387                    String::from_utf8(Vec::from(cmd)).unwrap_or_else(|_| "decode error".to_owned()),
388                ),
389            )));
390        }
391
392        next_cmd
393            .responses
394            .and_then(|values| match values.as_slice() {
395                [value] => Ok(value.clone()),
396                [] => {
397                    self.assert_is_empty_on_drop = false;
398                    Err(RedisError::from((
399                    ErrorKind::Client,
400                    "no value configured as response",
401                )))},
402                _ => {
403                    self.assert_is_empty_on_drop = false;
404                    Err(RedisError::from((
405                    ErrorKind::Client,
406                    "multiple values configured as response for command expecting a single value",
407                )))},
408            })
409    }
410
411    fn req_packed_commands(
412        &mut self,
413        cmd: &[u8],
414        _offset: usize,
415        _count: usize,
416    ) -> RedisResult<Vec<Value>> {
417        let mut commands = self.commands.lock().unwrap();
418        let next_cmd = commands.pop_front().ok_or_else(|| {
419            RedisError::from((ErrorKind::Client, "TEST", "unexpected command".to_owned()))
420        })?;
421
422        if cmd != next_cmd.cmd_bytes {
423            return Err(RedisError::from((
424                ErrorKind::Client,
425                "TEST",
426                format!(
427                    "unexpected command: expected={}, actual={}",
428                    String::from_utf8(next_cmd.cmd_bytes)
429                        .unwrap_or_else(|_| "decode error".to_owned()),
430                    String::from_utf8(Vec::from(cmd)).unwrap_or_else(|_| "decode error".to_owned()),
431                ),
432            )));
433        }
434
435        next_cmd.responses
436    }
437
438    fn get_db(&self) -> i64 {
439        0
440    }
441
442    fn check_connection(&mut self) -> bool {
443        true
444    }
445
446    fn is_open(&self) -> bool {
447        true
448    }
449}
450
451#[cfg(feature = "aio")]
452impl AioConnectionLike for MockRedisConnection {
453    fn req_packed_command<'a>(&'a mut self, cmd: &'a Cmd) -> RedisFuture<'a, Value> {
454        let packed_cmd = cmd.get_packed_command();
455        let response = <MockRedisConnection as ConnectionLike>::req_packed_command(
456            self,
457            packed_cmd.as_slice(),
458        );
459        future::ready(response).boxed()
460    }
461
462    fn req_packed_commands<'a>(
463        &'a mut self,
464        cmd: &'a Pipeline,
465        offset: usize,
466        count: usize,
467    ) -> RedisFuture<'a, Vec<Value>> {
468        let packed_cmd = cmd.get_packed_pipeline();
469        let response = <MockRedisConnection as ConnectionLike>::req_packed_commands(
470            self,
471            packed_cmd.as_slice(),
472            offset,
473            count,
474        );
475        future::ready(response).boxed()
476    }
477
478    fn get_db(&self) -> i64 {
479        0
480    }
481}
482
483#[cfg(test)]
484mod tests {
485    use super::{IntoRedisValue, MockCmd, MockRedisConnection};
486    use redis::{ErrorKind, ServerError, Value, cmd, make_extension_error, pipe};
487    use std::collections::{HashMap, HashSet};
488
489    #[test]
490    fn into_redis_value_i8() {
491        assert_eq!(42_i8.into_redis_value(), Value::Int(42));
492    }
493
494    #[test]
495    fn into_redis_value_i16() {
496        assert_eq!(42_i16.into_redis_value(), Value::Int(42));
497    }
498
499    #[test]
500    fn into_redis_value_i32() {
501        assert_eq!(42_i32.into_redis_value(), Value::Int(42));
502    }
503
504    #[test]
505    fn into_redis_value_i64() {
506        assert_eq!(42_i64.into_redis_value(), Value::Int(42));
507    }
508
509    #[test]
510    fn into_redis_value_u8() {
511        assert_eq!(42_u8.into_redis_value(), Value::Int(42));
512    }
513
514    #[test]
515    fn into_redis_value_u16() {
516        assert_eq!(42_u16.into_redis_value(), Value::Int(42));
517    }
518
519    #[test]
520    fn into_redis_value_u32() {
521        assert_eq!(42_u32.into_redis_value(), Value::Int(42));
522    }
523
524    #[test]
525    fn into_redis_value_string() {
526        let input = "foo".to_string();
527
528        let actual = input.into_redis_value();
529
530        let expected = Value::BulkString(vec![
531            0x66, /* f */
532            0x6f, /* o */
533            0x6f, /* o */
534        ]);
535        assert_eq!(actual, expected);
536    }
537
538    #[test]
539    fn into_redis_value_str_ref() {
540        let input = "foo";
541
542        let actual = input.into_redis_value();
543
544        let expected = Value::BulkString(vec![
545            0x66, /* f */
546            0x6f, /* o */
547            0x6f, /* o */
548        ]);
549        assert_eq!(actual, expected);
550    }
551
552    #[test]
553    fn into_redis_value_bool_true() {
554        assert_eq!(true.into_redis_value(), Value::Boolean(true));
555    }
556
557    #[test]
558    fn into_redis_value_bool_false() {
559        assert_eq!(false.into_redis_value(), Value::Boolean(false));
560    }
561
562    #[cfg(feature = "bytes")]
563    #[test]
564    fn into_redis_value_bytes() {
565        let input = bytes::Bytes::from("foo");
566
567        let actual = input.into_redis_value();
568
569        let expected = Value::BulkString(vec![
570            0x66, /* f */
571            0x6f, /* o */
572            0x6f, /* o */
573        ]);
574        assert_eq!(actual, expected);
575    }
576
577    #[test]
578    fn into_redis_value_vec_u8() {
579        let input = vec![0x66 /* f */, 0x6f /* o */, 0x6f /* o */];
580
581        let actual = input.into_redis_value();
582
583        let expected = Value::BulkString(vec![
584            0x66, /* f */
585            0x6f, /* o */
586            0x6f, /* o */
587        ]);
588        assert_eq!(actual, expected);
589    }
590
591    #[test]
592    fn into_redis_value_vec_value() {
593        let input = vec![Value::Int(42), Value::Boolean(true)];
594
595        let actual = input.into_redis_value();
596
597        let expected = Value::Array(vec![Value::Int(42), Value::Boolean(true)]);
598        assert_eq!(actual, expected);
599    }
600
601    #[test]
602    fn into_redis_value_vec_value_value() {
603        let input = vec![
604            (Value::Int(42), Value::Boolean(true)),
605            (Value::Int(23), Value::Nil),
606        ];
607
608        let actual = input.into_redis_value();
609
610        let expected = Value::Map(vec![
611            (Value::Int(42), Value::Boolean(true)),
612            (Value::Int(23), Value::Nil),
613        ]);
614        assert_eq!(actual, expected);
615    }
616
617    #[test]
618    fn into_redis_value_hashmap() {
619        let input = HashMap::from([(23, true), (42, false)]);
620
621        let actual = input.into_redis_value();
622
623        let mut actual_entries = actual
624            .into_map_iter()
625            .expect("extracting elements should work")
626            .collect::<Vec<(Value, Value)>>();
627
628        // Sorting the entries, to make sure they are in the order that we expect
629        actual_entries.sort_by(|a, b| {
630            let Value::Int(int_key_a) = a.0 else {
631                panic!("left-hand argument has to be a `Value::Int`");
632            };
633            let Value::Int(int_key_b) = b.0 else {
634                panic!("right-hand argument has to be a `Value::Int`");
635            };
636            int_key_a.cmp(&int_key_b)
637        });
638
639        let expected_entries = vec![
640            (Value::Int(23), Value::Boolean(true)),
641            (Value::Int(42), Value::Boolean(false)),
642        ];
643
644        assert_eq!(actual_entries, expected_entries);
645    }
646
647    #[test]
648    fn into_redis_value_hashset() {
649        let input = HashSet::from([23, 42]);
650
651        let actual = input.into_redis_value();
652
653        let mut actual_entries = actual
654            .into_sequence()
655            .expect("extracting elements should work");
656
657        // Sorting the entries, to make sure they are in the order that we expect
658        actual_entries.sort_by(|a, b| {
659            let Value::Int(int_a) = a else {
660                panic!("left-hand argument has to be a `Value::Int`");
661            };
662            let Value::Int(int_b) = b else {
663                panic!("right-hand argument has to be a `Value::Int`");
664            };
665            int_a.cmp(int_b)
666        });
667
668        let expected_entries = vec![Value::Int(23), Value::Int(42)];
669
670        assert_eq!(actual_entries, expected_entries);
671    }
672
673    #[test]
674    fn into_redis_value_value() {
675        let input = Value::Int(42);
676
677        let actual = input.into_redis_value();
678
679        assert_eq!(actual, Value::Int(42));
680    }
681
682    #[test]
683    fn into_redis_value_server_error() {
684        let server_error = ServerError::try_from(make_extension_error("FOO".to_string(), None))
685            .expect("conversion should work");
686
687        let actual = server_error.clone().into_redis_value();
688
689        assert_eq!(actual, Value::ServerError(server_error));
690    }
691
692    #[test]
693    fn redis_simple_direct() {
694        assert_eq!(
695            redis_value!(simple:"foo"),
696            Value::SimpleString("foo".to_string())
697        );
698    }
699
700    #[test]
701    fn redis_simple_in_complex() {
702        let actual = redis_value!([(simple:"foo")]);
703
704        let expected = Value::Array(vec![Value::SimpleString("foo".to_string())]);
705        assert_eq!(actual, expected);
706    }
707
708    #[test]
709    fn redis_nil() {
710        assert_eq!(redis_value!(nil), Value::Nil);
711    }
712
713    #[test]
714    fn redis_ok() {
715        assert_eq!(redis_value!(ok), Value::Okay);
716    }
717
718    #[test]
719    fn redis_okay() {
720        assert_eq!(redis_value!(okay), Value::Okay);
721    }
722
723    #[test]
724    fn redis_i8() {
725        assert_eq!(redis_value!(42_i8), Value::Int(42));
726    }
727
728    #[test]
729    fn redis_i16() {
730        assert_eq!(redis_value!(42_i16), Value::Int(42));
731    }
732
733    #[test]
734    fn redis_i32() {
735        assert_eq!(redis_value!(42_i32), Value::Int(42));
736    }
737
738    #[test]
739    fn redis_i64() {
740        assert_eq!(redis_value!(42_i64), Value::Int(42));
741    }
742
743    #[test]
744    fn redis_u8() {
745        assert_eq!(redis_value!(42_u8), Value::Int(42));
746    }
747
748    #[test]
749    fn redis_u16() {
750        assert_eq!(redis_value!(42_u16), Value::Int(42));
751    }
752
753    #[test]
754    fn redis_u32() {
755        assert_eq!(redis_value!(42_u32), Value::Int(42));
756    }
757
758    #[test]
759    fn redis_string() {
760        let actual = redis_value!("foo".to_string());
761
762        let expected = Value::BulkString(vec![
763            0x66, /* f */
764            0x6f, /* o */
765            0x6f, /* o */
766        ]);
767        assert_eq!(actual, expected);
768    }
769
770    #[test]
771    fn redis_str_ref() {
772        let actual = redis_value!("foo");
773
774        let expected = Value::BulkString(vec![
775            0x66, /* f */
776            0x6f, /* o */
777            0x6f, /* o */
778        ]);
779        assert_eq!(actual, expected);
780    }
781
782    #[test]
783    fn redis_true() {
784        assert_eq!(redis_value!(true), Value::Boolean(true));
785    }
786
787    #[test]
788    fn redis_false() {
789        assert_eq!(redis_value!(false), Value::Boolean(false));
790    }
791
792    #[test]
793    fn redis_value() {
794        let actual = Value::Int(42);
795
796        assert_eq!(redis_value!(actual), Value::Int(42));
797    }
798
799    #[test]
800    fn redis_array_empty() {
801        assert_eq!(redis_value!([]), Value::Array(vec![]));
802    }
803
804    #[test]
805    fn redis_array_single_entry() {
806        let actual = redis_value!([42]);
807
808        let expected = Value::Array(vec![Value::Int(42)]);
809        assert_eq!(actual, expected);
810    }
811
812    #[test]
813    fn redis_array_single_entry_trailing_comma() {
814        let actual = redis_value!([42,]);
815
816        let expected = Value::Array(vec![Value::Int(42)]);
817        assert_eq!(actual, expected);
818    }
819
820    #[test]
821    fn redis_array_multiple_primitive_entries() {
822        let last_arg = Value::Boolean(true); // pass the last arg in as variable
823        let actual = redis_value!([42, "foo", nil, last_arg]);
824
825        let expected1 = Value::Int(42);
826        let expected2 = Value::BulkString(vec![
827            0x66, /* f */
828            0x6f, /* o */
829            0x6f, /* o */
830        ]);
831        let expected3 = Value::Nil;
832        let expected4 = Value::Boolean(true);
833        let expected = Value::Array(vec![expected1, expected2, expected3, expected4]);
834        assert_eq!(actual, expected);
835    }
836
837    #[test]
838    fn redis_array_multiple_entries() {
839        let last_arg = Value::Boolean(true); // pass the last arg in as variable
840        let actual = redis_value!([42, ["foo", nil,], last_arg]);
841
842        let expected1 = Value::Int(42);
843        let expected21 = Value::BulkString(vec![
844            0x66, /* f */
845            0x6f, /* o */
846            0x6f, /* o */
847        ]);
848        let expected22 = Value::Nil;
849        let expected2 = Value::Array(vec![expected21, expected22]);
850        let expected3 = Value::Boolean(true);
851        let expected = Value::Array(vec![expected1, expected2, expected3]);
852        assert_eq!(actual, expected);
853    }
854
855    #[test]
856    fn redis_set_empty() {
857        assert_eq!(redis_value!(set:[]), Value::Set(vec![]));
858    }
859
860    #[test]
861    fn redis_set_single_entry() {
862        let actual = redis_value!(set:[42]);
863
864        let expected = Value::Set(vec![Value::Int(42)]);
865        assert_eq!(actual, expected);
866    }
867
868    #[test]
869    fn redis_set_single_entry_trailing_comma() {
870        let actual = redis_value!(set:[42,]);
871
872        let expected = Value::Set(vec![Value::Int(42)]);
873        assert_eq!(actual, expected);
874    }
875
876    #[test]
877    fn redis_set_multiple_primitive_entries() {
878        let last_arg = Value::Boolean(true); // pass the last arg in as variable
879        let actual = redis_value!(set:[42, "foo", nil, last_arg]);
880
881        let expected1 = Value::Int(42);
882        let expected2 = Value::BulkString(vec![
883            0x66, /* f */
884            0x6f, /* o */
885            0x6f, /* o */
886        ]);
887        let expected3 = Value::Nil;
888        let expected4 = Value::Boolean(true);
889        let expected = Value::Set(vec![expected1, expected2, expected3, expected4]);
890        assert_eq!(actual, expected);
891    }
892
893    #[test]
894    fn redis_set_multiple_entries() {
895        let last_arg = Value::Boolean(true); // pass the last arg in as variable
896        let actual = redis_value!(set:[42, (set:["foo", nil,]), last_arg]);
897
898        let expected1 = Value::Int(42);
899        let expected21 = Value::BulkString(vec![
900            0x66, /* f */
901            0x6f, /* o */
902            0x6f, /* o */
903        ]);
904        let expected22 = Value::Nil;
905        let expected2 = Value::Set(vec![expected21, expected22]);
906        let expected3 = Value::Boolean(true);
907        let expected = Value::Set(vec![expected1, expected2, expected3]);
908        assert_eq!(actual, expected);
909    }
910
911    #[test]
912    fn redis_map_empty() {
913        assert_eq!(redis_value!({}), Value::Map(vec![]));
914    }
915
916    #[test]
917    fn redis_map_single_entry() {
918        let actual = redis_value!({42: true});
919
920        let expected = Value::Map(vec![(Value::Int(42), Value::Boolean(true))]);
921        assert_eq!(actual, expected);
922    }
923
924    #[test]
925    fn redis_map_single_entry_trailing_comma() {
926        let actual = redis_value!({42: true,});
927
928        let expected = Value::Map(vec![(Value::Int(42), Value::Boolean(true))]);
929        assert_eq!(actual, expected);
930    }
931
932    #[test]
933    fn redis_map_multiple_primitive_entries() {
934        let actual = redis_value!({42: true, nil: "foo"});
935
936        let expected1 = (Value::Int(42), Value::Boolean(true));
937        let expected2 = (
938            Value::Nil,
939            Value::BulkString(vec![
940                0x66, /* f */
941                0x6f, /* o */
942                0x6f, /* o */
943            ]),
944        );
945        let expected = Value::Map(vec![expected1, expected2]);
946        assert_eq!(actual, expected);
947    }
948
949    #[test]
950    fn redis_map_multiple_entries() {
951        let actual = redis_value!({[42, false]: {true: [23, 4711],}, nil: "foo"});
952
953        let expected1_key = Value::Array(vec![Value::Int(42), Value::Boolean(false)]);
954        let expected1_value_key = Value::Boolean(true);
955        let expected1_value_value = Value::Array(vec![Value::Int(23), Value::Int(4711)]);
956        let expected1_value = Value::Map(vec![(expected1_value_key, expected1_value_value)]);
957        let expected1 = (expected1_key, expected1_value);
958        let expected2 = (
959            Value::Nil,
960            Value::BulkString(vec![
961                0x66, /* f */
962                0x6f, /* o */
963                0x6f, /* o */
964            ]),
965        );
966        let expected = Value::Map(vec![expected1, expected2]);
967        assert_eq!(actual, expected);
968    }
969
970    #[test]
971    fn sync_basic_test() {
972        let mut conn = MockRedisConnection::new(vec![
973            MockCmd::new(cmd("SET").arg("foo").arg(42), Ok("")),
974            MockCmd::new(cmd("GET").arg("foo"), Ok(42)),
975            MockCmd::new(cmd("SET").arg("bar").arg("foo"), Ok("")),
976            MockCmd::new(cmd("GET").arg("bar"), Ok("foo")),
977        ])
978        .assert_all_commands_consumed();
979
980        cmd("SET").arg("foo").arg(42).exec(&mut conn).unwrap();
981        assert_eq!(cmd("GET").arg("foo").query(&mut conn), Ok(42));
982
983        cmd("SET").arg("bar").arg("foo").exec(&mut conn).unwrap();
984        assert_eq!(
985            cmd("GET").arg("bar").query(&mut conn),
986            Ok(Value::BulkString(b"foo".as_ref().into()))
987        );
988    }
989
990    #[cfg(feature = "aio")]
991    #[tokio::test]
992    async fn async_basic_test() {
993        let mut conn = MockRedisConnection::new(vec![
994            MockCmd::new(cmd("SET").arg("foo").arg(42), Ok("")),
995            MockCmd::new(cmd("GET").arg("foo"), Ok(42)),
996            MockCmd::new(cmd("SET").arg("bar").arg("foo"), Ok("")),
997            MockCmd::new(cmd("GET").arg("bar"), Ok("foo")),
998        ])
999        .assert_all_commands_consumed();
1000
1001        cmd("SET")
1002            .arg("foo")
1003            .arg("42")
1004            .exec_async(&mut conn)
1005            .await
1006            .unwrap();
1007        let result: Result<usize, _> = cmd("GET").arg("foo").query_async(&mut conn).await;
1008        assert_eq!(result, Ok(42));
1009
1010        cmd("SET")
1011            .arg("bar")
1012            .arg("foo")
1013            .exec_async(&mut conn)
1014            .await
1015            .unwrap();
1016        let result: Result<Vec<u8>, _> = cmd("GET").arg("bar").query_async(&mut conn).await;
1017        assert_eq!(result.as_deref(), Ok(&b"foo"[..]));
1018    }
1019
1020    #[test]
1021    fn errors_for_unexpected_commands() {
1022        let mut conn = MockRedisConnection::new(vec![
1023            MockCmd::new(cmd("SET").arg("foo").arg(42), Ok("")),
1024            MockCmd::new(cmd("GET").arg("foo"), Ok(42)),
1025        ])
1026        .assert_all_commands_consumed();
1027
1028        cmd("SET").arg("foo").arg(42).exec(&mut conn).unwrap();
1029        assert_eq!(cmd("GET").arg("foo").query(&mut conn), Ok(42));
1030
1031        let err = cmd("SET")
1032            .arg("bar")
1033            .arg("foo")
1034            .exec(&mut conn)
1035            .unwrap_err();
1036        assert_eq!(err.kind(), ErrorKind::Client);
1037        assert_eq!(err.detail(), Some("unexpected command"));
1038    }
1039
1040    #[test]
1041    fn errors_for_mismatched_commands() {
1042        let mut conn = MockRedisConnection::new(vec![
1043            MockCmd::new(cmd("SET").arg("foo").arg(42), Ok("")),
1044            MockCmd::new(cmd("GET").arg("foo"), Ok(42)),
1045            MockCmd::new(cmd("SET").arg("bar").arg("foo"), Ok("")),
1046        ])
1047        .assert_all_commands_consumed();
1048
1049        cmd("SET").arg("foo").arg(42).exec(&mut conn).unwrap();
1050        let err = cmd("SET")
1051            .arg("bar")
1052            .arg("foo")
1053            .exec(&mut conn)
1054            .unwrap_err();
1055        assert_eq!(err.kind(), ErrorKind::Client);
1056        assert!(err.detail().unwrap().contains("unexpected command"));
1057    }
1058
1059    #[test]
1060    fn pipeline_basic_test() {
1061        let mut conn = MockRedisConnection::new(vec![MockCmd::with_values(
1062            pipe().cmd("GET").arg("foo").cmd("GET").arg("bar"),
1063            Ok(vec!["hello", "world"]),
1064        )])
1065        .assert_all_commands_consumed();
1066
1067        let results: Vec<String> = pipe()
1068            .cmd("GET")
1069            .arg("foo")
1070            .cmd("GET")
1071            .arg("bar")
1072            .query(&mut conn)
1073            .expect("success");
1074        assert_eq!(results, vec!["hello", "world"]);
1075    }
1076
1077    #[test]
1078    fn pipeline_atomic_test() {
1079        let mut conn = MockRedisConnection::new(vec![MockCmd::with_values(
1080            pipe().atomic().cmd("GET").arg("foo").cmd("GET").arg("bar"),
1081            Ok(vec![Value::Array(
1082                vec!["hello", "world"]
1083                    .into_iter()
1084                    .map(|x| Value::BulkString(x.as_bytes().into()))
1085                    .collect(),
1086            )]),
1087        )])
1088        .assert_all_commands_consumed();
1089
1090        let results: Vec<String> = pipe()
1091            .atomic()
1092            .cmd("GET")
1093            .arg("foo")
1094            .cmd("GET")
1095            .arg("bar")
1096            .query(&mut conn)
1097            .expect("success");
1098        assert_eq!(results, vec!["hello", "world"]);
1099    }
1100}