use crate::{
collection::{HasId, ProfileId, RecipeId},
database::CollectionDatabase,
http::{
Exchange, HttpEngine, RequestSeed, StoredRequestError,
TriggeredRequestError,
},
render::{HttpProvider, Prompter, SelectOption, TemplateContext},
};
use async_trait::async_trait;
use indexmap::IndexMap;
use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
use rstest::fixture;
use slumber_config::HttpEngineConfig;
use slumber_template::{Template, Value};
use std::{
hash::Hash,
sync::atomic::{AtomicUsize, Ordering},
};
pub fn invalid_utf8() -> Template {
"{{ b'\\xc3\\x28' }}".into()
}
#[fixture]
pub fn http_engine() -> HttpEngine {
HttpEngine::new(&HttpEngineConfig {
ignore_certificate_hosts: vec!["danger".to_owned()],
..Default::default()
})
}
#[derive(Debug)]
pub struct TestHttpProvider {
database: CollectionDatabase,
http_engine: Option<HttpEngine>,
}
impl TestHttpProvider {
pub fn new(
database: CollectionDatabase,
http_engine: Option<HttpEngine>,
) -> Self {
Self {
database,
http_engine,
}
}
}
#[async_trait(?Send)]
impl HttpProvider for TestHttpProvider {
async fn get_latest_request(
&self,
profile_id: Option<&ProfileId>,
recipe_id: &RecipeId,
) -> Result<Option<Exchange>, StoredRequestError> {
self.database
.get_latest_request(profile_id.into(), recipe_id)
.map_err(StoredRequestError::new)
}
async fn send_request(
&self,
seed: RequestSeed,
template_context: &TemplateContext,
) -> Result<Exchange, TriggeredRequestError> {
if let Some(http_engine) = &self.http_engine {
let ticket = http_engine.build(seed, template_context).await?;
let exchange = ticket.send(None).await?;
Ok(exchange)
} else {
Err(TriggeredRequestError::NotAllowed)
}
}
}
#[derive(Debug, Default)]
pub struct TestPrompter {
responses: Vec<String>,
index: AtomicUsize,
}
impl TestPrompter {
pub fn new<T: Into<String>>(
responses: impl IntoIterator<Item = T>,
) -> Self {
Self {
responses: responses.into_iter().map(T::into).collect(),
index: 0.into(),
}
}
}
#[async_trait(?Send)]
impl Prompter for TestPrompter {
async fn prompt_text(
&self,
_message: String,
default: Option<String>,
_sensitive: bool,
) -> Option<String> {
let index = self.index.fetch_add(1, Ordering::Relaxed);
self.responses.get(index).cloned().or(default)
}
async fn prompt_select(
&self,
_message: String,
_options: Vec<SelectOption>,
) -> Option<Value> {
unimplemented!("TestPrompter does not support selects")
}
}
#[derive(Debug, Default)]
pub struct TestSelectPrompter {
responses: Vec<usize>,
index: AtomicUsize,
}
impl TestSelectPrompter {
pub fn new(responses: impl IntoIterator<Item = usize>) -> Self {
Self {
responses: responses.into_iter().collect(),
index: 0.into(),
}
}
}
#[async_trait(?Send)]
impl Prompter for TestSelectPrompter {
async fn prompt_text(
&self,
_message: String,
_default: Option<String>,
_sensitive: bool,
) -> Option<String> {
unimplemented!("TestSelectPrompter does not support text prompts")
}
async fn prompt_select(
&self,
_message: String,
mut options: Vec<SelectOption>,
) -> Option<Value> {
let index = self.index.fetch_add(1, Ordering::Relaxed);
self.responses
.get(index)
.map(|value_index| options.swap_remove(*value_index).value)
}
}
pub fn by_id<T>(values: impl IntoIterator<Item = T>) -> IndexMap<T::Id, T>
where
T: HasId,
T::Id: Clone + Eq + Hash,
{
values
.into_iter()
.map(|value| (value.id().clone(), value))
.collect()
}
pub fn header_map<'a>(
headers: impl IntoIterator<Item = (&'a str, &'a str)>,
) -> HeaderMap {
headers
.into_iter()
.map(|(header, value)| {
(
HeaderName::try_from(header).unwrap(),
HeaderValue::try_from(value).unwrap(),
)
})
.collect()
}