roas_arazzo_executor/
testing.rs1use crate::http::{
10 AsyncHttpClient, ClientError, HttpClient, HttpRequest, HttpResponse, SendFuture, SleepFuture,
11};
12use serde_json::Value;
13use std::time::Duration;
14
15#[derive(Clone, Debug, Default)]
17pub struct Fake {
18 replies: Vec<HttpResponse>,
19 sent: Vec<HttpRequest>,
21 waited: Vec<Duration>,
23 at: usize,
24}
25
26impl Fake {
27 #[must_use]
29 pub fn new() -> Self {
30 Self::default()
31 }
32
33 #[must_use]
35 pub fn reply(mut self, status: u16, body: &Value) -> Self {
36 self.replies.push(HttpResponse::json(status, body));
37 self
38 }
39
40 #[must_use]
42 pub fn reply_with(mut self, response: HttpResponse) -> Self {
43 self.replies.push(response);
44 self
45 }
46
47 #[must_use]
49 pub fn sent(&self) -> &[HttpRequest] {
50 &self.sent
51 }
52
53 #[must_use]
56 pub fn waited(&self) -> &[Duration] {
57 &self.waited
58 }
59
60 fn answer(&mut self, request: &HttpRequest) -> Result<HttpResponse, ClientError> {
61 self.sent.push(request.clone());
62 let reply = self.replies.get(self.at).cloned().ok_or_else(|| {
63 ClientError(format!(
64 "the script has {} replies, and this is request {}: {} {}",
65 self.replies.len(),
66 self.at + 1,
67 request.method,
68 request.url,
69 ))
70 })?;
71 self.at += 1;
72 Ok(reply)
73 }
74}
75
76impl HttpClient for Fake {
77 fn send(&mut self, request: &HttpRequest) -> Result<HttpResponse, ClientError> {
78 self.answer(request)
79 }
80}
81
82impl AsyncHttpClient for Fake {
83 fn send<'a>(&'a mut self, request: &'a HttpRequest) -> SendFuture<'a> {
84 let answer = self.answer(request);
85 Box::pin(std::future::ready(answer))
86 }
87
88 fn sleep(&self, _duration: Duration) -> SleepFuture<'_> {
90 Box::pin(std::future::ready(()))
91 }
92}
93
94impl Fake {
101 pub fn note_wait(&mut self, duration: Duration) {
103 self.waited.push(duration);
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110 use serde_json::json;
111
112 fn request(url: &str) -> HttpRequest {
113 HttpRequest {
114 method: "GET".to_owned(),
115 url: url.to_owned(),
116 ..HttpRequest::default()
117 }
118 }
119
120 #[test]
121 fn replies_come_back_in_the_order_they_were_scripted() {
122 let mut fake = Fake::new()
123 .reply(200, &json!({ "first": true }))
124 .reply_with(HttpResponse {
125 status: 503,
126 headers: Vec::new(),
127 body: Vec::new(),
128 });
129 let first = HttpClient::send(&mut fake, &request("https://example.com/a")).unwrap();
130 assert_eq!(first.body_as_json(), Some(json!({ "first": true })));
131 let second = HttpClient::send(&mut fake, &request("https://example.com/b")).unwrap();
132 assert_eq!(second.status, 503);
133 assert_eq!(fake.sent().len(), 2);
134 assert_eq!(fake.sent()[1].url, "https://example.com/b");
135 }
136
137 #[test]
138 fn running_out_of_script_says_which_request_it_was() {
139 let mut fake = Fake::new();
140 let error = HttpClient::send(&mut fake, &request("https://example.com/a")).unwrap_err();
141 assert_eq!(
142 error.to_string(),
143 "the script has 0 replies, and this is request 1: GET https://example.com/a"
144 );
145 }
146
147 #[test]
148 fn the_async_client_answers_from_the_same_script() {
149 let mut fake = Fake::new().reply(204, &json!(null));
150 let response = futures_lite(AsyncHttpClient::send(
151 &mut fake,
152 &request("https://example.com/a"),
153 ));
154 assert_eq!(response.unwrap().status, 204);
155 futures_lite(AsyncHttpClient::sleep(&fake, Duration::from_secs(60)));
156 assert_eq!(fake.sent().len(), 1);
157 }
158
159 #[test]
160 fn a_wait_can_be_noted_rather_than_spent() {
161 let mut fake = Fake::new();
162 fake.note_wait(Duration::from_millis(1500));
163 assert_eq!(fake.waited(), [Duration::from_millis(1500)]);
164 }
165
166 fn futures_lite<F: std::future::Future>(future: F) -> F::Output {
170 use std::pin::pin;
171 use std::task::{Context, Poll, Waker};
172 let mut future = pin!(future);
173 match future
174 .as_mut()
175 .poll(&mut Context::from_waker(Waker::noop()))
176 {
177 Poll::Ready(output) => output,
178 Poll::Pending => panic!("the fake client is never pending"),
179 }
180 }
181}