use crate::{error, Cache};
use std::sync::Arc;
use http_cache::*;
use reqwest::Client;
use reqwest_middleware::ClientBuilder;
use url::Url;
use wiremock::{matchers::method, Mock, MockServer, ResponseTemplate};
pub(crate) fn build_mock(
cache_control_val: &str,
body: &[u8],
status: u16,
expect: u64,
) -> Mock {
Mock::given(method(GET))
.respond_with(
ResponseTemplate::new(status)
.insert_header("cache-control", cache_control_val)
.set_body_bytes(body),
)
.expect(expect)
}
const GET: &str = "GET";
const TEST_BODY: &[u8] = b"test";
const CACHEABLE_PUBLIC: &str = "max-age=86400, public";
#[test]
#[allow(clippy::default_constructed_unit_structs)]
fn test_errors() -> Result<()> {
let br = error::BadRequest::default();
assert_eq!(format!("{:?}", br.clone()), "BadRequest",);
assert_eq!(
br.to_string(),
"Request object is not cloneable. Are you passing a streaming body?"
.to_string(),
);
Ok(())
}
#[tokio::test]
async fn default_mode() -> Result<()> {
let mock_server = MockServer::start().await;
let m = build_mock(CACHEABLE_PUBLIC, TEST_BODY, 200, 1);
let _mock_guard = mock_server.register_as_scoped(m).await;
let url = format!("{}/", &mock_server.uri());
let manager = MokaManager::default();
let client = ClientBuilder::new(Client::new())
.with(Cache(HttpCache {
mode: CacheMode::Default,
manager: manager.clone(),
options: HttpCacheOptions::default(),
}))
.build();
client.get(url.clone()).send().await?;
let data = manager.get(&format!("{}:{}", GET, &Url::parse(&url)?)).await?;
assert!(data.is_some());
let res = client.get(url).send().await?;
assert_eq!(res.bytes().await?, TEST_BODY);
Ok(())
}
#[tokio::test]
async fn default_mode_with_options() -> Result<()> {
let mock_server = MockServer::start().await;
let m = build_mock(CACHEABLE_PUBLIC, TEST_BODY, 200, 1);
let _mock_guard = mock_server.register_as_scoped(m).await;
let url = format!("{}/", &mock_server.uri());
let manager = MokaManager::default();
let client = ClientBuilder::new(Client::new())
.with(Cache(HttpCache {
mode: CacheMode::Default,
manager: manager.clone(),
options: HttpCacheOptions {
cache_key: None,
cache_options: Some(CacheOptions {
shared: false,
..Default::default()
}),
cache_mode_fn: None,
cache_bust: None,
cache_status_headers: true,
},
}))
.build();
client.get(url.clone()).send().await?;
let data = manager.get(&format!("{}:{}", GET, &Url::parse(&url)?)).await?;
assert!(data.is_some());
Ok(())
}
#[tokio::test]
async fn no_cache_mode() -> Result<()> {
let mock_server = MockServer::start().await;
let m = build_mock(CACHEABLE_PUBLIC, TEST_BODY, 200, 2);
let _mock_guard = mock_server.register_as_scoped(m).await;
let url = format!("{}/", &mock_server.uri());
let manager = MokaManager::default();
let client = ClientBuilder::new(Client::new())
.with(Cache(HttpCache {
mode: CacheMode::NoCache,
manager: manager.clone(),
options: HttpCacheOptions::default(),
}))
.build();
client.get(url.clone()).send().await?;
let data = manager.get(&format!("{}:{}", GET, &Url::parse(&url)?)).await?;
assert!(data.is_some());
client.get(url).send().await?;
Ok(())
}
#[tokio::test]
async fn reload_mode() -> Result<()> {
let mock_server = MockServer::start().await;
let m = build_mock(CACHEABLE_PUBLIC, TEST_BODY, 200, 2);
let _mock_guard = mock_server.register_as_scoped(m).await;
let url = format!("{}/", &mock_server.uri());
let manager = MokaManager::default();
let client = ClientBuilder::new(Client::new())
.with(Cache(HttpCache {
mode: CacheMode::Reload,
manager: manager.clone(),
options: HttpCacheOptions {
cache_key: None,
cache_options: Some(CacheOptions {
shared: false,
..Default::default()
}),
cache_mode_fn: None,
cache_bust: None,
cache_status_headers: true,
},
}))
.build();
client.get(url.clone()).send().await?;
let data = manager.get(&format!("{}:{}", GET, &Url::parse(&url)?)).await?;
assert!(data.is_some());
client.get(url).send().await?;
Ok(())
}
#[tokio::test]
async fn custom_cache_key() -> Result<()> {
let mock_server = MockServer::start().await;
let m = build_mock(CACHEABLE_PUBLIC, TEST_BODY, 200, 1);
let _mock_guard = mock_server.register_as_scoped(m).await;
let url = format!("{}/", &mock_server.uri());
let manager = MokaManager::default();
let client = ClientBuilder::new(Client::new())
.with(Cache(HttpCache {
mode: CacheMode::Default,
manager: manager.clone(),
options: HttpCacheOptions {
cache_key: Some(Arc::new(|req: &http::request::Parts| {
format!("{}:{}:{:?}:test", req.method, req.uri, req.version)
})),
cache_options: None,
cache_mode_fn: None,
cache_bust: None,
cache_status_headers: true,
},
}))
.build();
client.get(url.clone()).send().await?;
let data = manager
.get(&format!("{}:{}:{:?}:test", GET, &url, http::Version::HTTP_11))
.await?;
assert!(data.is_some());
Ok(())
}
#[tokio::test]
async fn custom_cache_mode_fn() -> Result<()> {
let mock_server = MockServer::start().await;
let m = build_mock(CACHEABLE_PUBLIC, TEST_BODY, 200, 2);
let _mock_guard = mock_server.register_as_scoped(m).await;
let url = format!("{}/test.css", &mock_server.uri());
let manager = MokaManager::default();
let client = ClientBuilder::new(Client::new())
.with(Cache(HttpCache {
mode: CacheMode::NoStore,
manager: manager.clone(),
options: HttpCacheOptions {
cache_key: None,
cache_options: None,
cache_mode_fn: Some(Arc::new(|req: &http::request::Parts| {
if req.uri.path().ends_with(".css") {
CacheMode::Default
} else {
CacheMode::NoStore
}
})),
cache_bust: None,
cache_status_headers: true,
},
}))
.build();
client.get(url.clone()).send().await?;
let data = manager.get(&format!("{}:{}", GET, &Url::parse(&url)?)).await?;
assert!(data.is_some());
let url = format!("{}/", &mock_server.uri());
client.get(url.clone()).send().await?;
let data = manager.get(&format!("{}:{}", GET, &Url::parse(&url)?)).await?;
assert!(data.is_none());
Ok(())
}
#[tokio::test]
async fn override_cache_mode() -> Result<()> {
let mock_server = MockServer::start().await;
let m = build_mock(CACHEABLE_PUBLIC, TEST_BODY, 200, 2);
let _mock_guard = mock_server.register_as_scoped(m).await;
let url = format!("{}/test.css", &mock_server.uri());
let manager = MokaManager::default();
let client = ClientBuilder::new(Client::new())
.with(Cache(HttpCache {
mode: CacheMode::Default,
manager: manager.clone(),
options: HttpCacheOptions {
cache_key: None,
cache_options: None,
cache_mode_fn: None,
cache_bust: None,
cache_status_headers: true,
},
}))
.build();
client.get(url.clone()).send().await?;
let data = manager.get(&format!("{}:{}", GET, &Url::parse(&url)?)).await?;
assert!(data.is_some());
let url = format!("{}/", &mock_server.uri());
client.get(url.clone()).with_extension(CacheMode::NoStore).send().await?;
let data = manager.get(&format!("{}:{}", GET, &Url::parse(&url)?)).await?;
assert!(data.is_none());
Ok(())
}
#[tokio::test]
async fn no_status_headers() -> Result<()> {
let mock_server = MockServer::start().await;
let m = build_mock(CACHEABLE_PUBLIC, TEST_BODY, 200, 1);
let _mock_guard = mock_server.register_as_scoped(m).await;
let url = format!("{}/test.css", &mock_server.uri());
let manager = MokaManager::default();
let client = ClientBuilder::new(Client::new())
.with(Cache(HttpCache {
mode: CacheMode::Default,
manager: manager.clone(),
options: HttpCacheOptions {
cache_key: None,
cache_options: None,
cache_mode_fn: None,
cache_bust: None,
cache_status_headers: false,
},
}))
.build();
let res = client.get(url.clone()).send().await?;
let data = manager.get(&format!("{}:{}", GET, &Url::parse(&url)?)).await?;
assert!(data.is_some());
assert!(res.headers().get(XCACHELOOKUP).is_none());
assert!(res.headers().get(XCACHE).is_none());
Ok(())
}
#[tokio::test]
async fn cache_bust() -> Result<()> {
let mock_server = MockServer::start().await;
let m = build_mock(CACHEABLE_PUBLIC, TEST_BODY, 200, 2);
let _mock_guard = mock_server.register_as_scoped(m).await;
let url = format!("{}/", &mock_server.uri());
let manager = MokaManager::default();
let client = ClientBuilder::new(Client::new())
.with(Cache(HttpCache {
mode: CacheMode::Default,
manager: manager.clone(),
options: HttpCacheOptions {
cache_key: None,
cache_options: None,
cache_mode_fn: None,
cache_bust: Some(Arc::new(
|req: &http::request::Parts, _, _| {
if req.uri.path().ends_with("/bust-cache") {
vec![format!(
"{}:{}://{}:{}/",
GET,
req.uri.scheme_str().unwrap(),
req.uri.host().unwrap(),
req.uri.port_u16().unwrap_or(80)
)]
} else {
Vec::new()
}
},
)),
cache_status_headers: true,
},
}))
.build();
client.get(url.clone()).send().await?;
let data = manager.get(&format!("{}:{}", GET, &Url::parse(&url)?)).await?;
assert!(data.is_some());
client.get(format!("{}/bust-cache", &mock_server.uri())).send().await?;
let data = manager.get(&format!("{}:{}", GET, &Url::parse(&url)?)).await?;
assert!(data.is_none());
Ok(())
}
#[tokio::test]
async fn delete_after_non_get_head_method_request() -> Result<()> {
let mock_server = MockServer::start().await;
let m = build_mock(CACHEABLE_PUBLIC, TEST_BODY, 200, 1);
let _mock_guard = mock_server.register_as_scoped(m).await;
let url = format!("{}/", &mock_server.uri());
let manager = MokaManager::default();
let client = ClientBuilder::new(Client::new())
.with(Cache(HttpCache {
mode: CacheMode::Default,
manager: manager.clone(),
options: HttpCacheOptions::default(),
}))
.build();
client.get(url.clone()).send().await?;
let data = manager.get(&format!("{}:{}", GET, &Url::parse(&url)?)).await?;
assert!(data.is_some());
client.post(url.clone()).send().await?;
let data = manager.get(&format!("{}:{}", GET, &Url::parse(&url)?)).await?;
assert!(data.is_none());
Ok(())
}