use crate::QuickManager;
use std::sync::Arc;
use http_cache::*;
use http_cache_reqwest::Cache;
use http_cache_semantics::CachePolicy;
use reqwest::Client;
use reqwest_middleware::ClientBuilder;
use wiremock::{matchers::method, Mock, MockServer, ResponseTemplate};
use macro_rules_attribute::apply;
use smol_macros::test;
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";
#[apply(test!)]
async fn quickcache() -> Result<()> {
assert_eq!(
format!("{:?}", QuickManager::default()),
"QuickManager { cache: \"Cache<String, Arc<Vec<u8>>>\", .. }",
);
let url = url_parse("http://example.com")?;
let manager = Arc::new(QuickManager::default());
let http_res = HttpResponse {
body: TEST_BODY.to_vec(),
headers: Default::default(),
status: 200,
url: url.clone(),
version: HttpVersion::Http11,
metadata: None,
};
let req = http::Request::get("http://example.com").body(())?;
let res = http::Response::builder().status(200).body(TEST_BODY.to_vec())?;
let policy = CachePolicy::new(&req, &res);
CacheManager::put(
&*manager,
format!("{}:{}", GET, &url),
http_res.clone(),
policy.clone(),
)
.await?;
let data =
CacheManager::get(&*manager, &format!("{}:{}", GET, &url)).await?;
assert!(data.is_some());
assert_eq!(data.unwrap().0.body, TEST_BODY);
CacheManager::delete(&*manager, &format!("{}:{}", GET, &url)).await?;
let data =
CacheManager::get(&*manager, &format!("{}:{}", GET, &url)).await?;
assert!(data.is_none());
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 = QuickManager::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 =
CacheManager::get(&manager, &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 = QuickManager::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,
response_cache_mode_fn: None,
cache_bust: None,
cache_status_headers: true,
max_ttl: None,
metadata_provider: None,
modify_response: None,
},
}))
.build();
client.get(url.clone()).send().await?;
let data = http_cache::CacheManager::get(
&manager,
&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 = QuickManager::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 = http_cache::CacheManager::get(
&manager,
&format!("{}:{}", GET, &url_parse(&url)?),
)
.await?;
assert!(data.is_some());
client.get(url).send().await?;
Ok(())
}
#[tokio::test]
async fn head_request_caching() -> Result<()> {
let mock_server = MockServer::start().await;
let m = Mock::given(method("HEAD"))
.respond_with(
ResponseTemplate::new(200)
.insert_header("cache-control", CACHEABLE_PUBLIC)
.insert_header("content-type", "text/plain")
.insert_header("content-length", "4"), )
.expect(1);
let _mock_guard = mock_server.register_as_scoped(m).await;
let url = format!("{}/", &mock_server.uri());
let manager = QuickManager::default();
let client = ClientBuilder::new(Client::new())
.with(Cache(HttpCache {
mode: CacheMode::Default,
manager: manager.clone(),
options: HttpCacheOptions::default(),
}))
.build();
let res = client.head(url.clone()).send().await?;
assert_eq!(res.status(), 200);
assert_eq!(res.headers().get("content-type").unwrap(), "text/plain");
let data = http_cache::CacheManager::get(
&manager,
&format!("HEAD:{}", &url_parse(&url)?),
)
.await?;
assert!(data.is_some());
let cached_response = data.unwrap().0;
assert_eq!(cached_response.status, 200);
Ok(())
}
#[tokio::test]
async fn put_request_invalidates_cache() -> Result<()> {
let mock_server = MockServer::start().await;
let m_get = Mock::given(method("GET"))
.respond_with(
ResponseTemplate::new(200)
.insert_header("cache-control", CACHEABLE_PUBLIC)
.set_body_bytes(TEST_BODY),
)
.expect(1);
let m_put = Mock::given(method("PUT"))
.respond_with(ResponseTemplate::new(204))
.expect(1);
let mock_guard_get = mock_server.register_as_scoped(m_get).await;
let url = format!("{}/", &mock_server.uri());
let manager = QuickManager::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 = http_cache::CacheManager::get(
&manager,
&format!("GET:{}", &url_parse(&url)?),
)
.await?;
assert!(data.is_some());
drop(mock_guard_get);
let _mock_guard_put = mock_server.register_as_scoped(m_put).await;
let put_res = client.put(url.clone()).send().await?;
assert_eq!(put_res.status(), 204);
let data = http_cache::CacheManager::get(
&manager,
&format!("GET:{}", &url_parse(&url)?),
)
.await?;
assert!(data.is_none());
Ok(())
}
#[tokio::test]
async fn patch_request_invalidates_cache() -> Result<()> {
let mock_server = MockServer::start().await;
let m_get = Mock::given(method("GET"))
.respond_with(
ResponseTemplate::new(200)
.insert_header("cache-control", CACHEABLE_PUBLIC)
.set_body_bytes(TEST_BODY),
)
.expect(1);
let m_patch = Mock::given(method("PATCH"))
.respond_with(ResponseTemplate::new(200))
.expect(1);
let mock_guard_get = mock_server.register_as_scoped(m_get).await;
let url = format!("{}/", &mock_server.uri());
let manager = QuickManager::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 = http_cache::CacheManager::get(
&manager,
&format!("GET:{}", &url_parse(&url)?),
)
.await?;
assert!(data.is_some());
drop(mock_guard_get);
let _mock_guard_patch = mock_server.register_as_scoped(m_patch).await;
let patch_res = client.patch(url.clone()).send().await?;
assert_eq!(patch_res.status(), 200);
let data = http_cache::CacheManager::get(
&manager,
&format!("GET:{}", &url_parse(&url)?),
)
.await?;
assert!(data.is_none());
Ok(())
}
#[tokio::test]
async fn delete_request_invalidates_cache() -> Result<()> {
let mock_server = MockServer::start().await;
let m_get = Mock::given(method("GET"))
.respond_with(
ResponseTemplate::new(200)
.insert_header("cache-control", CACHEABLE_PUBLIC)
.set_body_bytes(TEST_BODY),
)
.expect(1);
let m_delete = Mock::given(method("DELETE"))
.respond_with(ResponseTemplate::new(204))
.expect(1);
let mock_guard_get = mock_server.register_as_scoped(m_get).await;
let url = format!("{}/", &mock_server.uri());
let manager = QuickManager::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 = http_cache::CacheManager::get(
&manager,
&format!("GET:{}", &url_parse(&url)?),
)
.await?;
assert!(data.is_some());
drop(mock_guard_get);
let _mock_guard_delete = mock_server.register_as_scoped(m_delete).await;
let delete_res = client.delete(url.clone()).send().await?;
assert_eq!(delete_res.status(), 204);
let data = http_cache::CacheManager::get(
&manager,
&format!("GET:{}", &url_parse(&url)?),
)
.await?;
assert!(data.is_none());
Ok(())
}
#[tokio::test]
async fn options_request_not_cached() -> Result<()> {
let mock_server = MockServer::start().await;
let m = Mock::given(method("OPTIONS"))
.respond_with(
ResponseTemplate::new(200)
.insert_header("allow", "GET, POST, PUT, DELETE")
.insert_header("cache-control", CACHEABLE_PUBLIC), )
.expect(2); let _mock_guard = mock_server.register_as_scoped(m).await;
let url = format!("{}/", &mock_server.uri());
let manager = QuickManager::default();
let client = ClientBuilder::new(Client::new())
.with(Cache(HttpCache {
mode: CacheMode::Default,
manager: manager.clone(),
options: HttpCacheOptions::default(),
}))
.build();
let res1 =
client.request(reqwest::Method::OPTIONS, url.clone()).send().await?;
assert_eq!(res1.status(), 200);
let data = http_cache::CacheManager::get(
&manager,
&format!("OPTIONS:{}", &url_parse(&url)?),
)
.await?;
assert!(data.is_none());
let res2 =
client.request(reqwest::Method::OPTIONS, url.clone()).send().await?;
assert_eq!(res2.status(), 200);
Ok(())
}