Skip to main content

taba_client/
testing.rs

1//! A fake transport for the tests of whoever uses the connector (`taba-cli` uses it): returns
2//! agreed responses and records what was sent.
3
4use std::collections::VecDeque;
5use std::sync::{Arc, Mutex};
6use std::time::Duration;
7
8use crate::client::{Client, Config};
9use crate::http::{Request, Response, Url};
10use crate::transport::{Timeouts, Transport, TransportError};
11
12/// What the transport received in a call.
13#[derive(Debug, Clone)]
14pub struct Seen {
15    /// `http://host:port` of the server called.
16    pub origin: String,
17    pub request: Request,
18    pub timeouts: Timeouts,
19}
20
21impl Seen {
22    pub fn header(&self, name: &str) -> Option<&str> {
23        self.request
24            .headers
25            .iter()
26            .find(|(k, _)| k.eq_ignore_ascii_case(name))
27            .map(|(_, v)| v.as_str())
28    }
29
30    pub fn body_text(&self) -> String {
31        String::from_utf8_lossy(&self.request.body).into_owned()
32    }
33}
34
35#[derive(Default)]
36pub struct MockTransport {
37    replies: Mutex<VecDeque<Result<Response, TransportError>>>,
38    seen: Mutex<Vec<Seen>>,
39}
40
41impl MockTransport {
42    pub fn new() -> Arc<MockTransport> {
43        Arc::new(MockTransport::default())
44    }
45
46    /// Agrees on the next response.
47    pub fn reply(&self, status: u16, headers: &[(&str, &str)], body: &str) {
48        self.replies.lock().unwrap().push_back(Ok(Response {
49            status,
50            headers: headers
51                .iter()
52                .map(|(k, v)| (k.to_string(), v.to_string()))
53                .collect(),
54            body: body.as_bytes().to_vec(),
55        }));
56    }
57
58    /// Agrees on a transport failure as the next response.
59    pub fn fail(&self, error: TransportError) {
60        self.replies.lock().unwrap().push_back(Err(error));
61    }
62
63    pub fn seen(&self) -> Vec<Seen> {
64        self.seen.lock().unwrap().clone()
65    }
66
67    /// Agreed responses that nobody used.
68    pub fn pending(&self) -> usize {
69        self.replies.lock().unwrap().len()
70    }
71}
72
73impl Transport for Arc<MockTransport> {
74    fn send(
75        &self,
76        url: &Url,
77        request: &Request,
78        timeouts: &Timeouts,
79    ) -> Result<Response, TransportError> {
80        self.seen.lock().unwrap().push(Seen {
81            origin: url.origin(),
82            request: request.clone(),
83            timeouts: *timeouts,
84        });
85        self.replies
86            .lock()
87            .unwrap()
88            .pop_front()
89            .unwrap_or(Err(TransportError::Connect(
90                "no agreed response in the test".into(),
91            )))
92    }
93}
94
95/// A client over the fake transport, without really waiting between attempts.
96pub fn client(config: Config, mock: &Arc<MockTransport>) -> Client {
97    Client::with_transport(config, Box::new(mock.clone()), Arc::new(|_: Duration| {}))
98        .expect("invalid test address")
99}