Skip to main content

redis_test/
lib.rs

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