use crate::prelude::*;
use std::sync::{Arc, RwLock};
mod store;
pub use store::*;
pub struct RecordReplay {
proxy: Proxy,
store: Arc<RwLock<dyn RecordReplayStore + Send + Sync>>,
}
impl RecordReplay {
pub async fn start<Store>(store: Store) -> Result<Self, CinemaError>
where
Store: RecordReplayStore + Send + Sync + 'static,
{
let store = Arc::new(RwLock::new(store));
let proxy = {
let store = store.clone();
Proxy::start_with(Arc::new(move |payload| {
let mut store = store
.write()
.map_err(|_| CinemaError::Lock("record replay store for writing"))?;
store.handle(payload)
}))
.await?
};
Ok(RecordReplay { proxy, store })
}
pub fn add_origin<Str: Into<String> + Copy>(&mut self, origin: Str) -> String {
let store = Arc::clone(&self.store);
self.proxy.forward_with(
origin,
Arc::new(move |payload| {
let record = RecordReplayRecord {
method: payload.request.method().clone(),
uri: payload.request.uri().clone(),
headers: None,
body: None,
response: RecordReplayRecordResponse {
status: payload.status,
headers: payload.headers.cloned(),
body: payload.body.clone(),
},
};
let mut store = store
.write()
.map_err(|_| CinemaError::Lock("record replay store for writing"))?;
store.record(record)?;
Ok(())
}),
)
}
pub async fn commit(&mut self) {
let store = self.store.clone();
let store = store
.read()
.map_err(|_| CinemaError::Lock("record replay store for reading"))
.unwrap();
store.commit().await.unwrap();
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
use reqwest::Client;
use tokio::fs;
#[tokio::test]
async fn test_replay() {
let store = RecordReplayFileStore::from("records/replay.toml")
.await
.unwrap();
let mut replay = RecordReplay::start(store).await.unwrap();
let mocked_origin = replay.add_origin("https://example.com");
let client = Client::new();
let response = client
.get(mocked_origin)
.send()
.await
.expect("Failed to send request");
let text = response.text().await.expect("Failed to read response");
assert_eq!(text, "<!doctype html><title>Hello, cruel world!</title>");
}
#[tokio::test]
async fn test_record() {
let fixture_name = "records/record.toml";
let _ = fs::remove_file(fixture_name).await.unwrap_or_else(|_| ());
let store = RecordReplayFileStore::from(fixture_name).await.unwrap();
let mut replay = RecordReplay::start(store).await.unwrap();
let mocked_origin = replay.add_origin("https://example.com");
let client = Client::new();
let response = client
.get(mocked_origin)
.send()
.await
.expect("Failed to send request");
let text = response.text().await.expect("Failed to read response");
replay.commit().await;
let records_str = fs::read_to_string(fixture_name)
.await
.expect("Failed to read file");
let records: RecordReplayRecords =
toml::from_str(&records_str).expect("Failed to parse TOML");
assert_eq!(records.requests.len(), 1);
let request = records.requests.get(0).expect("Failed to find request");
assert_eq!(request.method, "GET");
assert_eq!(request.uri, "https://example.com/");
assert_eq!(request.response.status, 200);
assert_eq!(request.response.body, text);
let _ = fs::remove_file(fixture_name).await.unwrap();
}
}