Skip to main content

rr_mux/
mock.rs

1use std::collections::VecDeque;
2use std::fmt::Debug;
3use std::marker::PhantomData;
4use std::sync::{Arc, Mutex};
5use std::time::Duration;
6
7use async_trait::async_trait;
8
9use derive_builder::Builder;
10
11use crate::connector::Connector;
12use crate::muxed::Muxed;
13
14/// MockRequest is a mocked request expectation with a provided response
15#[derive(Debug, Clone, PartialEq, Builder)]
16pub struct MockRequest<Addr, Req, Resp, Ctx, E> {
17    to: Addr,
18    req: Req,
19    ctx: Option<Ctx>,
20
21    resp: Result<(Resp, Ctx), E>,
22    delay: Option<Duration>,
23}
24
25impl<Addr, Req, Resp, Ctx, E> MockRequest<Addr, Req, Resp, Ctx, E> {
26    /// Create a new mock request.
27    /// You probably want to use MockTransaction::request instead of constructing this directly
28    pub fn new(to: Addr, req: Req, resp: Result<(Resp, Ctx), E>) -> Self {
29        MockRequest {
30            to,
31            req,
32            resp,
33            ctx: None,
34            delay: None,
35        }
36    }
37
38    pub fn with_context(mut self, ctx: Ctx) -> Self {
39        self.ctx = Some(ctx);
40        self
41    }
42}
43
44/// MockResponse is a mocked response expectation
45#[derive(Debug, Clone, PartialEq, Builder)]
46pub struct MockResponse<Addr, Resp, Ctx, E> {
47    to: Addr,
48    resp: Resp,
49    err: Option<E>,
50    ctx: Option<Ctx>,
51}
52
53impl<Addr, Resp, Ctx, E> MockResponse<Addr, Resp, Ctx, E> {
54    /// Create a new mock response.
55    /// You probably want to use MockTransaction::response instead of constructing this directly
56    pub fn new(to: Addr, resp: Resp, err: Option<E>) -> Self {
57        MockResponse {
58            to,
59            resp,
60            err,
61            ctx: None,
62        }
63    }
64
65    pub fn with_error(mut self, err: E) -> Self {
66        self.err = Some(err);
67        self
68    }
69
70    pub fn with_context(mut self, ctx: Ctx) -> Self {
71        self.ctx = Some(ctx);
72        self
73    }
74}
75
76// MockTransaction is a transaction expectation
77pub type MockTransaction<Addr, Req, Resp, Ctx, E> =
78    Muxed<MockRequest<Addr, Req, Resp, Ctx, E>, MockResponse<Addr, Resp, Ctx, E>>;
79
80impl<Addr, Req, Resp, Ctx, E> MockTransaction<Addr, Req, Resp, Ctx, E> {
81    /// Create a mock request -> response transaction
82    pub fn request(
83        to: Addr, req: Req, resp: Result<(Resp, Ctx), E>,
84    ) -> MockTransaction<Addr, Req, Resp, Ctx,  E> {
85        Muxed::Request(MockRequest::new(to, req, resp))
86    }
87
88    /// Create a mock response transaction
89    pub fn response(to: Addr, resp: Resp, err: Option<E>) -> MockTransaction<Addr, Req, Resp, Ctx, E> {
90        Muxed::Response(MockResponse::new(to, resp, err))
91    }
92}
93
94/// MockConnector provides an expectation based mock connector implementation
95/// to simplify writing tests against modules using the Connector abstraction.
96pub struct MockConnector<Addr, Req, Resp, E, Ctx> {
97    transactions: Arc<Mutex<VecDeque<MockTransaction<Addr, Req, Resp, Ctx, E>>>>,
98    _ctx: PhantomData<Ctx>,
99}
100
101impl<Addr, Req, Resp, E, Ctx> Clone for MockConnector<Addr, Req, Resp, E, Ctx> {
102    fn clone(&self) -> Self {
103        MockConnector {
104            transactions: self.transactions.clone(),
105            _ctx: PhantomData,
106        }
107    }
108}
109
110impl<Addr, Req, Resp, E, Ctx> MockConnector<Addr, Req, Resp, E, Ctx>
111where
112    Addr: PartialEq + Debug + Send + 'static,
113    Req: PartialEq + Debug + Send + 'static,
114    Resp: PartialEq + Debug + Send + 'static,
115    E: PartialEq + Debug + Send + 'static,
116    Ctx: Clone + PartialEq + Debug + Send + 'static,
117{
118    /// Create a new mock connector
119    pub fn new() -> MockConnector<Addr, Req, Resp, E, Ctx> {
120        MockConnector {
121            transactions: Arc::new(Mutex::new(VecDeque::new())),
122            _ctx: PhantomData,
123        }
124    }
125
126    /// Set expectations on the connector
127    pub fn expect<T>(&mut self, transactions: T) -> Self
128    where
129        T: Into<VecDeque<MockTransaction<Addr, Req, Resp, Ctx, E>>>,
130    {
131        *self.transactions.lock().unwrap() = transactions.into();
132
133        self.clone()
134    }
135
136    /// Finalise expectations on the connector
137    pub fn finalise(&mut self) {
138        let transactions: Vec<_> = self.transactions.lock().unwrap().drain(..).collect();
139        let expectations = Vec::<MockTransaction<Addr, Req, Resp, Ctx, E>>::new();
140        assert_eq!(
141            expectations, transactions,
142            "not all transactions have been evaluated"
143        );
144    }
145}
146
147#[async_trait]
148impl<Id, Addr, Req, Resp, E, Ctx> Connector<Id, Addr, Req, Resp, E, Ctx>
149    for MockConnector<Addr, Req, Resp, E, Ctx>
150where
151    Id: PartialEq + Debug + Send + 'static,
152    Addr: PartialEq + Debug + Send + 'static,
153    Req: PartialEq + Debug + Send + 'static,
154    Resp: PartialEq + Debug + Send + 'static,
155    E: PartialEq + Debug + Send + 'static,
156    Ctx: Clone + PartialEq + Debug + Send + 'static,
157{
158    /// Make a request and return the pre-set response
159    /// This checks the request against the specified expectations
160    async fn request(
161        &mut self, ctx: Ctx, _id: Id, addr: Addr, req: Req,
162    ) -> Result<Resp, E> {
163        let mut transactions = self.transactions.lock().unwrap();
164
165        let transaction = transactions.pop_front().expect(&format!(
166            "request error, no more transactions available (request: {:?})",
167            req
168        ));
169        let request = transaction.req().expect("expected request");
170
171        assert_eq!(request.to, addr, "destination mismatch");
172        assert_eq!(request.req, req, "request mismatch");
173        if let Some(c) = request.ctx {
174            assert_eq!(c, ctx, "context mismatch");
175        }
176
177        match request.resp {
178            Ok(r) => Ok(r.0),
179            Err(e) => Err(e),
180        }
181    }
182
183    /// Make a response
184    /// This checks the response against provided expectations
185    async fn respond(
186        &mut self, ctx: Ctx, _id: Id, addr: Addr, resp: Resp,
187    ) -> Result<(), E> {
188        let mut transactions = self.transactions.lock().unwrap();
189
190        let transaction = transactions.pop_front().expect(&format!(
191            "response error, no more transactions available (response: {:?})",
192            resp
193        ));
194        let response = transaction.resp().expect("expected response");
195
196        assert_eq!(response.to, addr, "destination mismatch");
197        assert_eq!(response.resp, resp, "request mismatch");
198        if let Some(c) = response.ctx {
199            assert_eq!(c, ctx, "context mismatch");
200        }
201
202        match response.err {
203            Some(e) => Err(e),
204            None => Ok(()),
205        }
206    }
207}