1#![allow(
10 clippy::unwrap_used,
11 clippy::expect_used,
12 reason = "a fake for tests says what went wrong by failing the test"
13)]
14
15use std::collections::{HashSet, VecDeque};
16use std::io;
17use std::sync::atomic::{AtomicBool, Ordering};
18use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
19use std::time::Duration;
20
21use async_trait::async_trait;
22use serde::Deserialize;
23use tokio::sync::{mpsc, watch};
24
25use crate::error::Error;
26use crate::protocol::{Command, Incoming, Protocol};
27use crate::transport::{Conn, DialOptions, Transport};
28use crate::v1_json::{SUBPROTOCOL_V1_JSON, V1Json};
29
30pub const WAIT: Duration = Duration::from_secs(2);
32
33const SILENCE: Duration = Duration::from_millis(100);
35
36#[derive(Clone)]
38pub struct FakeTransport {
39 shared: Arc<TransportState>,
40}
41
42struct TransportState {
43 subprotocol: Mutex<Option<String>>,
44 write_buffer: Mutex<usize>,
45 dialed: mpsc::UnboundedSender<Arc<FakeConn>>,
46 accepting: tokio::sync::Mutex<mpsc::UnboundedReceiver<Arc<FakeConn>>>,
47 dial_errors: Mutex<VecDeque<String>>,
48 options: Mutex<Option<DialOptions>>,
49}
50
51impl FakeTransport {
52 pub fn new() -> FakeTransport {
55 let (dialed, accepting) = mpsc::unbounded_channel();
56 FakeTransport {
57 shared: Arc::new(TransportState {
58 subprotocol: Mutex::new(Some(SUBPROTOCOL_V1_JSON.to_string())),
59 write_buffer: Mutex::new(32),
60 dialed,
61 accepting: tokio::sync::Mutex::new(accepting),
62 dial_errors: Mutex::new(VecDeque::new()),
63 options: Mutex::new(None),
64 }),
65 }
66 }
67
68 pub fn speak(&self, subprotocol: Option<&str>) {
70 *lock(&self.shared.subprotocol) = subprotocol.map(str::to_string);
71 }
72
73 pub fn write_buffer(&self, commands: usize) {
76 *lock(&self.shared.write_buffer) = commands.max(1);
77 }
78
79 pub fn fail_next_dial(&self, reason: &str) {
81 lock(&self.shared.dial_errors).push_back(reason.to_string());
82 }
83
84 pub fn dialed_with(&self) -> DialOptions {
86 lock(&self.shared.options)
87 .clone()
88 .expect("the client never dialed")
89 }
90
91 pub async fn accept(&self) -> Arc<FakeConn> {
93 tokio::time::timeout(WAIT, self.shared.accepting.lock().await.recv())
94 .await
95 .expect("no connection was dialed")
96 .unwrap()
97 }
98
99 pub async fn refuse_dial(&self) {
101 let dialed = tokio::time::timeout(SILENCE, self.shared.accepting.lock().await.recv()).await;
102 if let Ok(Some(conn)) = dialed {
103 panic!(
104 "expected no connection, got one with subprotocol {:?}",
105 conn.subprotocol
106 );
107 }
108 }
109}
110
111impl Default for FakeTransport {
112 fn default() -> FakeTransport {
113 FakeTransport::new()
114 }
115}
116
117#[async_trait]
118impl Transport for FakeTransport {
119 async fn dial(&self, _url: &str, options: DialOptions) -> Result<Box<dyn Conn>, Error> {
120 *lock(&self.shared.options) = Some(options);
121 if let Some(reason) = lock(&self.shared.dial_errors).pop_front() {
122 return Err(Error::transport(io::Error::new(
123 io::ErrorKind::ConnectionRefused,
124 reason,
125 )));
126 }
127
128 let conn = Arc::new(FakeConn::new(
129 lock(&self.shared.subprotocol).clone(),
130 *lock(&self.shared.write_buffer),
131 ));
132 self.shared.dialed.send(Arc::clone(&conn)).unwrap();
133 Ok(Box::new(ClientEnd(conn)))
134 }
135}
136
137pub struct FakeConn {
141 subprotocol: Option<String>,
142 incoming: mpsc::Sender<Vec<u8>>,
143 reading: tokio::sync::Mutex<mpsc::Receiver<Vec<u8>>>,
144 outgoing: mpsc::Sender<Vec<u8>>,
145 sent: tokio::sync::Mutex<mpsc::Receiver<Vec<u8>>>,
146 writes: watch::Sender<usize>,
149 confirmed: Mutex<HashSet<String>>,
150 failing_writes: AtomicBool,
151 closed: watch::Sender<bool>,
152}
153
154impl FakeConn {
155 fn new(subprotocol: Option<String>, write_buffer: usize) -> FakeConn {
156 let (incoming, reading) = mpsc::channel(1);
157 let (outgoing, sent) = mpsc::channel(write_buffer);
158 FakeConn {
159 subprotocol,
160 incoming,
161 reading: tokio::sync::Mutex::new(reading),
162 outgoing,
163 sent: tokio::sync::Mutex::new(sent),
164 writes: watch::Sender::new(0),
165 confirmed: Mutex::new(HashSet::new()),
166 failing_writes: AtomicBool::new(false),
167 closed: watch::Sender::new(false),
168 }
169 }
170
171 pub async fn push(&self, frame: &str) {
173 let mut closed = self.closed.subscribe();
174 tokio::select! {
175 biased;
176 _ = closed.wait_for(|closed| *closed) => panic!("connection closed before {frame} could be sent"),
177 sent = tokio::time::timeout(WAIT, self.incoming.send(frame.as_bytes().to_vec())) => {
178 sent.unwrap_or_else(|_| panic!("client never read {frame}")).unwrap_or_else(|_| panic!("client hung up before reading {frame}"));
179 }
180 }
181 }
182
183 pub async fn welcome(&self) {
185 self.push(r#"{"type":"welcome"}"#).await;
186 }
187
188 pub async fn confirm(&self, identifier: &str) {
190 lock(&self.confirmed).insert(identifier.to_string());
191 self.push(&format!(
192 r#"{{"type":"confirm_subscription","identifier":{}}}"#,
193 quote(identifier)
194 ))
195 .await;
196 }
197
198 pub async fn reject(&self, identifier: &str) {
200 lock(&self.confirmed).remove(identifier);
201 self.push(&format!(
202 r#"{{"type":"reject_subscription","identifier":{}}}"#,
203 quote(identifier)
204 ))
205 .await;
206 }
207
208 pub async fn broadcast(&self, identifier: &str, message: &str) {
210 self.push(&format!(
211 r#"{{"identifier":{},"message":{message}}}"#,
212 quote(identifier)
213 ))
214 .await;
215 }
216
217 pub fn fail_writes(&self) {
220 self.failing_writes.store(true, Ordering::SeqCst);
221 }
222
223 pub async fn writing(&self, count: usize) {
226 let mut writes = self.writes.subscribe();
227 tokio::time::timeout(WAIT, writes.wait_for(|writes| *writes >= count))
228 .await
229 .expect("the client wrote less than that")
230 .unwrap();
231 }
232
233 pub async fn sent(&self) -> Vec<u8> {
235 self.next_acted_on(WAIT).await.expect("client sent nothing")
236 }
237
238 async fn next_acted_on(&self, wait: Duration) -> Option<Vec<u8>> {
242 let mut sent = self.sent.lock().await;
243 loop {
244 match tokio::time::timeout(wait, sent.recv()).await {
245 Ok(Some(payload)) if self.acts_on(&payload) => break Some(payload),
246 Ok(Some(_ignored)) => {}
247 Ok(None) | Err(_) => break None,
248 }
249 }
250 }
251
252 fn acts_on(&self, payload: &[u8]) -> bool {
253 match serde_json::from_slice::<SentCommand>(payload) {
254 Ok(command) if command.command == "subscribe" => {
255 !lock(&self.confirmed).contains(&command.identifier)
256 }
257 Ok(command) if command.command == "unsubscribe" => {
258 lock(&self.confirmed).remove(&command.identifier);
259 true
260 }
261 _ => true,
262 }
263 }
264
265 pub async fn command(&self) -> SentCommand {
267 let payload = self.sent().await;
268 serde_json::from_slice(&payload).unwrap_or_else(|error| {
269 panic!(
270 "decoding command {}: {error}",
271 String::from_utf8_lossy(&payload)
272 )
273 })
274 }
275
276 pub async fn expect_command(&self, name: &str, identifier: &str) -> SentCommand {
280 let command = self.command().await;
281 assert_eq!(
282 name, command.command,
283 "expected {name} {identifier}, got {command:?}"
284 );
285 assert_eq!(
286 identifier, command.identifier,
287 "expected {name} {identifier}, got {command:?}"
288 );
289 command
290 }
291
292 pub async fn expect_silence(&self) {
294 if let Some(payload) = self.next_acted_on(SILENCE).await {
295 panic!(
296 "expected no command, got {}",
297 String::from_utf8_lossy(&payload)
298 );
299 }
300 }
301
302 pub fn close(&self) {
304 self.closed.send_replace(true);
305 }
306}
307
308struct ClientEnd(Arc<FakeConn>);
309
310#[async_trait]
311impl Conn for ClientEnd {
312 fn subprotocol(&self) -> Option<&str> {
313 self.0.subprotocol.as_deref()
314 }
315
316 async fn read(&self) -> Result<Vec<u8>, Error> {
317 let mut closed = self.0.closed.subscribe();
318 let mut reading = self.0.reading.lock().await;
319 tokio::select! {
320 biased;
321 _ = closed.wait_for(|closed| *closed) => Err(eof()),
322 payload = reading.recv() => payload.ok_or_else(eof),
323 }
324 }
325
326 async fn write(&self, payload: &[u8]) -> Result<(), Error> {
327 if self.0.failing_writes.load(Ordering::SeqCst) {
328 return Err(Error::transport(io::Error::new(
329 io::ErrorKind::TimedOut,
330 "the peer stopped reading",
331 )));
332 }
333 self.0.writes.send_modify(|writes| *writes += 1);
334
335 let mut closed = self.0.closed.subscribe();
336 tokio::select! {
337 biased;
338 _ = closed.wait_for(|closed| *closed) => Err(eof()),
339 sent = self.0.outgoing.send(payload.to_vec()) => sent.map_err(|_| eof()),
340 }
341 }
342
343 async fn close(&self) {
344 self.0.close();
345 }
346}
347
348fn lock<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
349 mutex.lock().unwrap_or_else(PoisonError::into_inner)
350}
351
352fn eof() -> Error {
353 Error::transport(io::Error::new(
354 io::ErrorKind::UnexpectedEof,
355 "connection closed",
356 ))
357}
358
359#[derive(Debug, Deserialize)]
361pub struct SentCommand {
362 pub command: String,
364 pub identifier: String,
366 pub data: Option<String>,
368}
369
370pub fn quote(value: &str) -> String {
372 serde_json::to_string(value).unwrap()
373}
374
375pub struct FakeProtocol {
378 pub subprotocol: String,
380 pub stamp: String,
382}
383
384impl FakeProtocol {
385 pub fn v2() -> FakeProtocol {
387 FakeProtocol {
388 subprotocol: "actioncable-v2-json".to_string(),
389 stamp: "v2:".to_string(),
390 }
391 }
392}
393
394impl Protocol for FakeProtocol {
395 fn subprotocol(&self) -> &str {
396 &self.subprotocol
397 }
398
399 fn encode(&self, command: &Command) -> Result<Vec<u8>, Error> {
400 let mut payload = self.stamp.as_bytes().to_vec();
401 payload.extend(V1Json.encode(command)?);
402 Ok(payload)
403 }
404
405 fn decode(&self, payload: &[u8]) -> Result<Incoming, Error> {
406 V1Json.decode(
407 payload
408 .strip_prefix(self.stamp.as_bytes())
409 .unwrap_or(payload),
410 )
411 }
412}