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`.
284#[derive(Clone)]
285pub struct MockCmd {
286    cmd_bytes: Vec<u8>,
287    responses: Result<Vec<Value>, RedisError>,
288}
289
290impl MockCmd {
291    /// Create a new `MockCmd` given a Redis command and either a value convertible to
292    /// a `redis::Value` or a `RedisError`.
293    pub fn new<C, V>(cmd: C, response: Result<V, RedisError>) -> Self
294    where
295        C: IntoRedisCmdBytes,
296        V: IntoRedisValue,
297    {
298        MockCmd {
299            cmd_bytes: cmd.into_redis_cmd_bytes(),
300            responses: response.map(|r| vec![r.into_redis_value()]),
301        }
302    }
303
304    /// Create a new `MockCommand` given a Redis command/pipeline and a vector of value convertible
305    /// to a `redis::Value` or a `RedisError`.
306    pub fn with_values<C, V>(cmd: C, responses: Result<Vec<V>, RedisError>) -> Self
307    where
308        C: IntoRedisCmdBytes,
309        V: IntoRedisValue,
310    {
311        MockCmd {
312            cmd_bytes: cmd.into_redis_cmd_bytes(),
313            responses: responses.map(|xs| xs.into_iter().map(|x| x.into_redis_value()).collect()),
314        }
315    }
316}
317
318/// A mock Redis client for testing without a server. `MockRedisConnection` checks whether the
319/// client submits a specific sequence of commands and generates an error if it does not.
320#[derive(Clone)]
321pub struct MockRedisConnection {
322    commands: Arc<Mutex<VecDeque<MockCmd>>>,
323    assert_is_empty_on_drop: bool,
324}
325
326impl MockRedisConnection {
327    /// Construct a new from the given sequence of commands.
328    pub fn new<I>(commands: I) -> Self
329    where
330        I: IntoIterator<Item = MockCmd>,
331    {
332        MockRedisConnection {
333            commands: Arc::new(Mutex::new(VecDeque::from_iter(commands))),
334            assert_is_empty_on_drop: false,
335        }
336    }
337
338    /// Enable assertion to ensure all commands have been consumed
339    pub fn assert_all_commands_consumed(mut self) -> Self {
340        self.assert_is_empty_on_drop = true;
341        self
342    }
343}
344
345impl Drop for MockRedisConnection {
346    fn drop(&mut self) {
347        if self.assert_is_empty_on_drop {
348            let commands = self.commands.lock().unwrap();
349            if Arc::strong_count(&self.commands) == 1 {
350                assert!(commands.back().is_none());
351            }
352        }
353    }
354}
355
356impl MockRedisConnection {
357    pub fn is_empty(&self) -> bool {
358        self.commands.lock().unwrap().is_empty()
359    }
360}
361
362impl ConnectionLike for MockRedisConnection {
363    fn req_packed_command(&mut self, cmd: &[u8]) -> RedisResult<Value> {
364        let mut commands = self.commands.lock().unwrap();
365        let next_cmd = commands.pop_front().ok_or_else(|| {
366            self.assert_is_empty_on_drop = false;
367            RedisError::from((ErrorKind::Client, "TEST", "unexpected command".to_owned()))
368        })?;
369
370        if cmd != next_cmd.cmd_bytes {
371            self.assert_is_empty_on_drop = false;
372            return Err(RedisError::from((
373                ErrorKind::Client,
374                "TEST",
375                format!(
376                    "unexpected command: expected={}, actual={}",
377                    String::from_utf8(next_cmd.cmd_bytes)
378                        .unwrap_or_else(|_| "decode error".to_owned()),
379                    String::from_utf8(Vec::from(cmd)).unwrap_or_else(|_| "decode error".to_owned()),
380                ),
381            )));
382        }
383
384        next_cmd
385            .responses
386            .and_then(|values| match values.as_slice() {
387                [value] => Ok(value.clone()),
388                [] => {
389                    self.assert_is_empty_on_drop = false;
390                    Err(RedisError::from((
391                    ErrorKind::Client,
392                    "no value configured as response",
393                )))},
394                _ => {
395                    self.assert_is_empty_on_drop = false;
396                    Err(RedisError::from((
397                    ErrorKind::Client,
398                    "multiple values configured as response for command expecting a single value",
399                )))},
400            })
401    }
402
403    fn req_packed_commands(
404        &mut self,
405        cmd: &[u8],
406        _offset: usize,
407        _count: usize,
408    ) -> RedisResult<Vec<Value>> {
409        let mut commands = self.commands.lock().unwrap();
410        let next_cmd = commands.pop_front().ok_or_else(|| {
411            RedisError::from((ErrorKind::Client, "TEST", "unexpected command".to_owned()))
412        })?;
413
414        if cmd != next_cmd.cmd_bytes {
415            return Err(RedisError::from((
416                ErrorKind::Client,
417                "TEST",
418                format!(
419                    "unexpected command: expected={}, actual={}",
420                    String::from_utf8(next_cmd.cmd_bytes)
421                        .unwrap_or_else(|_| "decode error".to_owned()),
422                    String::from_utf8(Vec::from(cmd)).unwrap_or_else(|_| "decode error".to_owned()),
423                ),
424            )));
425        }
426
427        next_cmd.responses
428    }
429
430    fn get_db(&self) -> i64 {
431        0
432    }
433
434    fn check_connection(&mut self) -> bool {
435        true
436    }
437
438    fn is_open(&self) -> bool {
439        true
440    }
441}
442
443#[cfg(feature = "aio")]
444impl AioConnectionLike for MockRedisConnection {
445    fn req_packed_command<'a>(&'a mut self, cmd: &'a Cmd) -> RedisFuture<'a, Value> {
446        let packed_cmd = cmd.get_packed_command();
447        let response = <MockRedisConnection as ConnectionLike>::req_packed_command(
448            self,
449            packed_cmd.as_slice(),
450        );
451        future::ready(response).boxed()
452    }
453
454    fn req_packed_commands<'a>(
455        &'a mut self,
456        cmd: &'a Pipeline,
457        offset: usize,
458        count: usize,
459    ) -> RedisFuture<'a, Vec<Value>> {
460        let packed_cmd = cmd.get_packed_pipeline();
461        let response = <MockRedisConnection as ConnectionLike>::req_packed_commands(
462            self,
463            packed_cmd.as_slice(),
464            offset,
465            count,
466        );
467        future::ready(response).boxed()
468    }
469
470    fn get_db(&self) -> i64 {
471        0
472    }
473}
474
475#[cfg(test)]
476mod tests {
477    use super::{IntoRedisValue, MockCmd, MockRedisConnection};
478    use redis::{ErrorKind, ServerError, Value, cmd, make_extension_error, pipe};
479    use std::collections::{HashMap, HashSet};
480
481    #[test]
482    fn into_redis_value_i8() {
483        assert_eq!(42_i8.into_redis_value(), Value::Int(42));
484    }
485
486    #[test]
487    fn into_redis_value_i16() {
488        assert_eq!(42_i16.into_redis_value(), Value::Int(42));
489    }
490
491    #[test]
492    fn into_redis_value_i32() {
493        assert_eq!(42_i32.into_redis_value(), Value::Int(42));
494    }
495
496    #[test]
497    fn into_redis_value_i64() {
498        assert_eq!(42_i64.into_redis_value(), Value::Int(42));
499    }
500
501    #[test]
502    fn into_redis_value_u8() {
503        assert_eq!(42_u8.into_redis_value(), Value::Int(42));
504    }
505
506    #[test]
507    fn into_redis_value_u16() {
508        assert_eq!(42_u16.into_redis_value(), Value::Int(42));
509    }
510
511    #[test]
512    fn into_redis_value_u32() {
513        assert_eq!(42_u32.into_redis_value(), Value::Int(42));
514    }
515
516    #[test]
517    fn into_redis_value_string() {
518        let input = "foo".to_string();
519
520        let actual = input.into_redis_value();
521
522        let expected = Value::BulkString(vec![
523            0x66, /* f */
524            0x6f, /* o */
525            0x6f, /* o */
526        ]);
527        assert_eq!(actual, expected);
528    }
529
530    #[test]
531    fn into_redis_value_str_ref() {
532        let input = "foo";
533
534        let actual = input.into_redis_value();
535
536        let expected = Value::BulkString(vec![
537            0x66, /* f */
538            0x6f, /* o */
539            0x6f, /* o */
540        ]);
541        assert_eq!(actual, expected);
542    }
543
544    #[test]
545    fn into_redis_value_bool_true() {
546        assert_eq!(true.into_redis_value(), Value::Boolean(true));
547    }
548
549    #[test]
550    fn into_redis_value_bool_false() {
551        assert_eq!(false.into_redis_value(), Value::Boolean(false));
552    }
553
554    #[cfg(feature = "bytes")]
555    #[test]
556    fn into_redis_value_bytes() {
557        let input = bytes::Bytes::from("foo");
558
559        let actual = input.into_redis_value();
560
561        let expected = Value::BulkString(vec![
562            0x66, /* f */
563            0x6f, /* o */
564            0x6f, /* o */
565        ]);
566        assert_eq!(actual, expected);
567    }
568
569    #[test]
570    fn into_redis_value_vec_u8() {
571        let input = vec![0x66 /* f */, 0x6f /* o */, 0x6f /* o */];
572
573        let actual = input.into_redis_value();
574
575        let expected = Value::BulkString(vec![
576            0x66, /* f */
577            0x6f, /* o */
578            0x6f, /* o */
579        ]);
580        assert_eq!(actual, expected);
581    }
582
583    #[test]
584    fn into_redis_value_vec_value() {
585        let input = vec![Value::Int(42), Value::Boolean(true)];
586
587        let actual = input.into_redis_value();
588
589        let expected = Value::Array(vec![Value::Int(42), Value::Boolean(true)]);
590        assert_eq!(actual, expected);
591    }
592
593    #[test]
594    fn into_redis_value_vec_value_value() {
595        let input = vec![
596            (Value::Int(42), Value::Boolean(true)),
597            (Value::Int(23), Value::Nil),
598        ];
599
600        let actual = input.into_redis_value();
601
602        let expected = Value::Map(vec![
603            (Value::Int(42), Value::Boolean(true)),
604            (Value::Int(23), Value::Nil),
605        ]);
606        assert_eq!(actual, expected);
607    }
608
609    #[test]
610    fn into_redis_value_hashmap() {
611        let input = HashMap::from([(23, true), (42, false)]);
612
613        let actual = input.into_redis_value();
614
615        let mut actual_entries = actual
616            .into_map_iter()
617            .expect("extracting elements should work")
618            .collect::<Vec<(Value, Value)>>();
619
620        // Sorting the entries, to make sure they are in the order that we expect
621        actual_entries.sort_by(|a, b| {
622            let Value::Int(int_key_a) = a.0 else {
623                panic!("left-hand argument has to be a `Value::Int`");
624            };
625            let Value::Int(int_key_b) = b.0 else {
626                panic!("right-hand argument has to be a `Value::Int`");
627            };
628            int_key_a.cmp(&int_key_b)
629        });
630
631        let expected_entries = vec![
632            (Value::Int(23), Value::Boolean(true)),
633            (Value::Int(42), Value::Boolean(false)),
634        ];
635
636        assert_eq!(actual_entries, expected_entries);
637    }
638
639    #[test]
640    fn into_redis_value_hashset() {
641        let input = HashSet::from([23, 42]);
642
643        let actual = input.into_redis_value();
644
645        let mut actual_entries = actual
646            .into_sequence()
647            .expect("extracting elements should work");
648
649        // Sorting the entries, to make sure they are in the order that we expect
650        actual_entries.sort_by(|a, b| {
651            let Value::Int(int_a) = a else {
652                panic!("left-hand argument has to be a `Value::Int`");
653            };
654            let Value::Int(int_b) = b else {
655                panic!("right-hand argument has to be a `Value::Int`");
656            };
657            int_a.cmp(int_b)
658        });
659
660        let expected_entries = vec![Value::Int(23), Value::Int(42)];
661
662        assert_eq!(actual_entries, expected_entries);
663    }
664
665    #[test]
666    fn into_redis_value_value() {
667        let input = Value::Int(42);
668
669        let actual = input.into_redis_value();
670
671        assert_eq!(actual, Value::Int(42));
672    }
673
674    #[test]
675    fn into_redis_value_server_error() {
676        let server_error = ServerError::try_from(make_extension_error("FOO".to_string(), None))
677            .expect("conversion should work");
678
679        let actual = server_error.clone().into_redis_value();
680
681        assert_eq!(actual, Value::ServerError(server_error));
682    }
683
684    #[test]
685    fn redis_simple_direct() {
686        assert_eq!(
687            redis_value!(simple:"foo"),
688            Value::SimpleString("foo".to_string())
689        );
690    }
691
692    #[test]
693    fn redis_simple_in_complex() {
694        let actual = redis_value!([(simple:"foo")]);
695
696        let expected = Value::Array(vec![Value::SimpleString("foo".to_string())]);
697        assert_eq!(actual, expected);
698    }
699
700    #[test]
701    fn redis_nil() {
702        assert_eq!(redis_value!(nil), Value::Nil);
703    }
704
705    #[test]
706    fn redis_ok() {
707        assert_eq!(redis_value!(ok), Value::Okay);
708    }
709
710    #[test]
711    fn redis_okay() {
712        assert_eq!(redis_value!(okay), Value::Okay);
713    }
714
715    #[test]
716    fn redis_i8() {
717        assert_eq!(redis_value!(42_i8), Value::Int(42));
718    }
719
720    #[test]
721    fn redis_i16() {
722        assert_eq!(redis_value!(42_i16), Value::Int(42));
723    }
724
725    #[test]
726    fn redis_i32() {
727        assert_eq!(redis_value!(42_i32), Value::Int(42));
728    }
729
730    #[test]
731    fn redis_i64() {
732        assert_eq!(redis_value!(42_i64), Value::Int(42));
733    }
734
735    #[test]
736    fn redis_u8() {
737        assert_eq!(redis_value!(42_u8), Value::Int(42));
738    }
739
740    #[test]
741    fn redis_u16() {
742        assert_eq!(redis_value!(42_u16), Value::Int(42));
743    }
744
745    #[test]
746    fn redis_u32() {
747        assert_eq!(redis_value!(42_u32), Value::Int(42));
748    }
749
750    #[test]
751    fn redis_string() {
752        let actual = redis_value!("foo".to_string());
753
754        let expected = Value::BulkString(vec![
755            0x66, /* f */
756            0x6f, /* o */
757            0x6f, /* o */
758        ]);
759        assert_eq!(actual, expected);
760    }
761
762    #[test]
763    fn redis_str_ref() {
764        let actual = redis_value!("foo");
765
766        let expected = Value::BulkString(vec![
767            0x66, /* f */
768            0x6f, /* o */
769            0x6f, /* o */
770        ]);
771        assert_eq!(actual, expected);
772    }
773
774    #[test]
775    fn redis_true() {
776        assert_eq!(redis_value!(true), Value::Boolean(true));
777    }
778
779    #[test]
780    fn redis_false() {
781        assert_eq!(redis_value!(false), Value::Boolean(false));
782    }
783
784    #[test]
785    fn redis_value() {
786        let actual = Value::Int(42);
787
788        assert_eq!(redis_value!(actual), Value::Int(42));
789    }
790
791    #[test]
792    fn redis_array_empty() {
793        assert_eq!(redis_value!([]), Value::Array(vec![]));
794    }
795
796    #[test]
797    fn redis_array_single_entry() {
798        let actual = redis_value!([42]);
799
800        let expected = Value::Array(vec![Value::Int(42)]);
801        assert_eq!(actual, expected);
802    }
803
804    #[test]
805    fn redis_array_single_entry_trailing_comma() {
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_multiple_primitive_entries() {
814        let last_arg = Value::Boolean(true); // pass the last arg in as variable
815        let actual = redis_value!([42, "foo", nil, last_arg]);
816
817        let expected1 = Value::Int(42);
818        let expected2 = Value::BulkString(vec![
819            0x66, /* f */
820            0x6f, /* o */
821            0x6f, /* o */
822        ]);
823        let expected3 = Value::Nil;
824        let expected4 = Value::Boolean(true);
825        let expected = Value::Array(vec![expected1, expected2, expected3, expected4]);
826        assert_eq!(actual, expected);
827    }
828
829    #[test]
830    fn redis_array_multiple_entries() {
831        let last_arg = Value::Boolean(true); // pass the last arg in as variable
832        let actual = redis_value!([42, ["foo", nil,], last_arg]);
833
834        let expected1 = Value::Int(42);
835        let expected21 = Value::BulkString(vec![
836            0x66, /* f */
837            0x6f, /* o */
838            0x6f, /* o */
839        ]);
840        let expected22 = Value::Nil;
841        let expected2 = Value::Array(vec![expected21, expected22]);
842        let expected3 = Value::Boolean(true);
843        let expected = Value::Array(vec![expected1, expected2, expected3]);
844        assert_eq!(actual, expected);
845    }
846
847    #[test]
848    fn redis_set_empty() {
849        assert_eq!(redis_value!(set:[]), Value::Set(vec![]));
850    }
851
852    #[test]
853    fn redis_set_single_entry() {
854        let actual = redis_value!(set:[42]);
855
856        let expected = Value::Set(vec![Value::Int(42)]);
857        assert_eq!(actual, expected);
858    }
859
860    #[test]
861    fn redis_set_single_entry_trailing_comma() {
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_multiple_primitive_entries() {
870        let last_arg = Value::Boolean(true); // pass the last arg in as variable
871        let actual = redis_value!(set:[42, "foo", nil, last_arg]);
872
873        let expected1 = Value::Int(42);
874        let expected2 = Value::BulkString(vec![
875            0x66, /* f */
876            0x6f, /* o */
877            0x6f, /* o */
878        ]);
879        let expected3 = Value::Nil;
880        let expected4 = Value::Boolean(true);
881        let expected = Value::Set(vec![expected1, expected2, expected3, expected4]);
882        assert_eq!(actual, expected);
883    }
884
885    #[test]
886    fn redis_set_multiple_entries() {
887        let last_arg = Value::Boolean(true); // pass the last arg in as variable
888        let actual = redis_value!(set:[42, (set:["foo", nil,]), last_arg]);
889
890        let expected1 = Value::Int(42);
891        let expected21 = Value::BulkString(vec![
892            0x66, /* f */
893            0x6f, /* o */
894            0x6f, /* o */
895        ]);
896        let expected22 = Value::Nil;
897        let expected2 = Value::Set(vec![expected21, expected22]);
898        let expected3 = Value::Boolean(true);
899        let expected = Value::Set(vec![expected1, expected2, expected3]);
900        assert_eq!(actual, expected);
901    }
902
903    #[test]
904    fn redis_map_empty() {
905        assert_eq!(redis_value!({}), Value::Map(vec![]));
906    }
907
908    #[test]
909    fn redis_map_single_entry() {
910        let actual = redis_value!({42: true});
911
912        let expected = Value::Map(vec![(Value::Int(42), Value::Boolean(true))]);
913        assert_eq!(actual, expected);
914    }
915
916    #[test]
917    fn redis_map_single_entry_trailing_comma() {
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_multiple_primitive_entries() {
926        let actual = redis_value!({42: true, nil: "foo"});
927
928        let expected1 = (Value::Int(42), Value::Boolean(true));
929        let expected2 = (
930            Value::Nil,
931            Value::BulkString(vec![
932                0x66, /* f */
933                0x6f, /* o */
934                0x6f, /* o */
935            ]),
936        );
937        let expected = Value::Map(vec![expected1, expected2]);
938        assert_eq!(actual, expected);
939    }
940
941    #[test]
942    fn redis_map_multiple_entries() {
943        let actual = redis_value!({[42, false]: {true: [23, 4711],}, nil: "foo"});
944
945        let expected1_key = Value::Array(vec![Value::Int(42), Value::Boolean(false)]);
946        let expected1_value_key = Value::Boolean(true);
947        let expected1_value_value = Value::Array(vec![Value::Int(23), Value::Int(4711)]);
948        let expected1_value = Value::Map(vec![(expected1_value_key, expected1_value_value)]);
949        let expected1 = (expected1_key, expected1_value);
950        let expected2 = (
951            Value::Nil,
952            Value::BulkString(vec![
953                0x66, /* f */
954                0x6f, /* o */
955                0x6f, /* o */
956            ]),
957        );
958        let expected = Value::Map(vec![expected1, expected2]);
959        assert_eq!(actual, expected);
960    }
961
962    #[test]
963    fn sync_basic_test() {
964        let mut conn = MockRedisConnection::new(vec![
965            MockCmd::new(cmd("SET").arg("foo").arg(42), Ok("")),
966            MockCmd::new(cmd("GET").arg("foo"), Ok(42)),
967            MockCmd::new(cmd("SET").arg("bar").arg("foo"), Ok("")),
968            MockCmd::new(cmd("GET").arg("bar"), Ok("foo")),
969        ])
970        .assert_all_commands_consumed();
971
972        cmd("SET").arg("foo").arg(42).exec(&mut conn).unwrap();
973        assert_eq!(cmd("GET").arg("foo").query(&mut conn), Ok(42));
974
975        cmd("SET").arg("bar").arg("foo").exec(&mut conn).unwrap();
976        assert_eq!(
977            cmd("GET").arg("bar").query(&mut conn),
978            Ok(Value::BulkString(b"foo".as_ref().into()))
979        );
980    }
981
982    #[cfg(feature = "aio")]
983    #[tokio::test]
984    async fn async_basic_test() {
985        let mut conn = MockRedisConnection::new(vec![
986            MockCmd::new(cmd("SET").arg("foo").arg(42), Ok("")),
987            MockCmd::new(cmd("GET").arg("foo"), Ok(42)),
988            MockCmd::new(cmd("SET").arg("bar").arg("foo"), Ok("")),
989            MockCmd::new(cmd("GET").arg("bar"), Ok("foo")),
990        ])
991        .assert_all_commands_consumed();
992
993        cmd("SET")
994            .arg("foo")
995            .arg("42")
996            .exec_async(&mut conn)
997            .await
998            .unwrap();
999        let result: Result<usize, _> = cmd("GET").arg("foo").query_async(&mut conn).await;
1000        assert_eq!(result, Ok(42));
1001
1002        cmd("SET")
1003            .arg("bar")
1004            .arg("foo")
1005            .exec_async(&mut conn)
1006            .await
1007            .unwrap();
1008        let result: Result<Vec<u8>, _> = cmd("GET").arg("bar").query_async(&mut conn).await;
1009        assert_eq!(result.as_deref(), Ok(&b"foo"[..]));
1010    }
1011
1012    #[test]
1013    fn errors_for_unexpected_commands() {
1014        let mut conn = MockRedisConnection::new(vec![
1015            MockCmd::new(cmd("SET").arg("foo").arg(42), Ok("")),
1016            MockCmd::new(cmd("GET").arg("foo"), Ok(42)),
1017        ])
1018        .assert_all_commands_consumed();
1019
1020        cmd("SET").arg("foo").arg(42).exec(&mut conn).unwrap();
1021        assert_eq!(cmd("GET").arg("foo").query(&mut conn), Ok(42));
1022
1023        let err = cmd("SET")
1024            .arg("bar")
1025            .arg("foo")
1026            .exec(&mut conn)
1027            .unwrap_err();
1028        assert_eq!(err.kind(), ErrorKind::Client);
1029        assert_eq!(err.detail(), Some("unexpected command"));
1030    }
1031
1032    #[test]
1033    fn errors_for_mismatched_commands() {
1034        let mut conn = MockRedisConnection::new(vec![
1035            MockCmd::new(cmd("SET").arg("foo").arg(42), Ok("")),
1036            MockCmd::new(cmd("GET").arg("foo"), Ok(42)),
1037            MockCmd::new(cmd("SET").arg("bar").arg("foo"), Ok("")),
1038        ])
1039        .assert_all_commands_consumed();
1040
1041        cmd("SET").arg("foo").arg(42).exec(&mut conn).unwrap();
1042        let err = cmd("SET")
1043            .arg("bar")
1044            .arg("foo")
1045            .exec(&mut conn)
1046            .unwrap_err();
1047        assert_eq!(err.kind(), ErrorKind::Client);
1048        assert!(err.detail().unwrap().contains("unexpected command"));
1049    }
1050
1051    #[test]
1052    fn pipeline_basic_test() {
1053        let mut conn = MockRedisConnection::new(vec![MockCmd::with_values(
1054            pipe().cmd("GET").arg("foo").cmd("GET").arg("bar"),
1055            Ok(vec!["hello", "world"]),
1056        )])
1057        .assert_all_commands_consumed();
1058
1059        let results: Vec<String> = pipe()
1060            .cmd("GET")
1061            .arg("foo")
1062            .cmd("GET")
1063            .arg("bar")
1064            .query(&mut conn)
1065            .expect("success");
1066        assert_eq!(results, vec!["hello", "world"]);
1067    }
1068
1069    #[test]
1070    fn pipeline_atomic_test() {
1071        let mut conn = MockRedisConnection::new(vec![MockCmd::with_values(
1072            pipe().atomic().cmd("GET").arg("foo").cmd("GET").arg("bar"),
1073            Ok(vec![Value::Array(
1074                vec!["hello", "world"]
1075                    .into_iter()
1076                    .map(|x| Value::BulkString(x.as_bytes().into()))
1077                    .collect(),
1078            )]),
1079        )])
1080        .assert_all_commands_consumed();
1081
1082        let results: Vec<String> = pipe()
1083            .atomic()
1084            .cmd("GET")
1085            .arg("foo")
1086            .cmd("GET")
1087            .arg("bar")
1088            .query(&mut conn)
1089            .expect("success");
1090        assert_eq!(results, vec!["hello", "world"]);
1091    }
1092}