Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 53 additions & 1 deletion http-cache-reqwest/src/test.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use crate::{BadRequest, Cache, HttpCacheError};
use std::sync::Arc;
use std::{
sync::Arc,
time::{Duration, SystemTime},
};

use crate::client_middleware::ClientBuilder;
use http_cache::*;
Expand Down Expand Up @@ -130,6 +133,55 @@ async fn default_mode_with_options() -> Result<()> {
Ok(())
}

#[tokio::test]
async fn max_ttl_applies_to_buffered_responses() -> 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 = create_cache_manager();

let client = ClientBuilder::new(Client::new())
.with(Cache(HttpCache {
mode: CacheMode::Default,
manager: manager.clone(),
options: HttpCacheOptions {
max_ttl: Some(Duration::ZERO),
..Default::default()
},
}))
.build();

// A zero maximum TTL still permits storage, but makes the stored policy
// immediately stale.
let response = client.get(&url).send().await?;
assert_eq!(response.bytes().await?, TEST_BODY);
let cached =
CacheManager::get(&manager, &format!("{}:{}", GET, url_parse(&url)?))
.await?;
let (cached_response, policy) = cached.expect("response should be stored");
assert_eq!(
cached_response.headers.get("cache-control").map(String::as_str),
Some(CACHEABLE_PUBLIC)
);
assert_eq!(policy.time_to_live(SystemTime::now()), Duration::ZERO);
assert!(policy.is_stale(SystemTime::now()));

// The next request must reach upstream because the initial policy is stale.
let response = client.get(&url).send().await?;
assert_eq!(response.bytes().await?, TEST_BODY);

// The replacement 200 response must also be stored with the capped policy.
let cached =
CacheManager::get(&manager, &format!("{}:{}", GET, url_parse(&url)?))
.await?;
let (_, policy) = cached.expect("replacement response should be stored");
assert_eq!(policy.time_to_live(SystemTime::now()), Duration::ZERO);
assert!(policy.is_stale(SystemTime::now()));

Ok(())
}

#[tokio::test]
async fn no_cache_mode() -> Result<()> {
let mock_server = MockServer::start().await;
Expand Down
17 changes: 6 additions & 11 deletions http-cache/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2938,13 +2938,10 @@ impl<T: CacheManager> HttpCache<T> {
res.cache_status(HitOrMiss::MISS);
res.cache_lookup_status(HitOrMiss::MISS);
}
let policy = match self.options.cache_options {
Some(options) => middleware.policy_with_options(&res, options)?,
None => middleware.policy(&res)?,
};
let parts = middleware.parts()?;
let policy = self.options.create_cache_policy(&parts, &res.parts()?);
let is_get_head = middleware.is_method_get_head();
let mut mode = self.cache_mode(middleware)?;
let parts = middleware.parts()?;

// Allow response-based cache mode override
if let Some(response_cache_mode_fn) =
Expand Down Expand Up @@ -3063,17 +3060,15 @@ impl<T: CacheManager> HttpCache<T> {
.await?;
Ok(res)
} else if cond_res.status == 200 {
let policy = match self.options.cache_options {
Some(options) => middleware
.policy_with_options(&cond_res, options)?,
None => middleware.policy(&cond_res)?,
};
let response_parts = cond_res.parts()?;
let policy = self
.options
.create_cache_policy(&parts, &response_parts);
if self.options.cache_status_headers {
cond_res.cache_status(HitOrMiss::MISS);
cond_res.cache_lookup_status(HitOrMiss::HIT);
}
// Generate metadata using the provider callback if configured
let response_parts = cond_res.parts()?;
cond_res.metadata =
self.options.generate_metadata(&parts, &response_parts);

Expand Down
52 changes: 51 additions & 1 deletion http-cache/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -808,7 +808,10 @@ mod interface_tests {
HttpCacheOptions,
};
use http::{Request, Response, StatusCode};
use std::sync::Arc;
use std::{
sync::Arc,
time::{Duration, SystemTime},
};

#[tokio::test]
async fn test_http_cache_interface_analyze_request() {
Expand Down Expand Up @@ -903,6 +906,53 @@ mod interface_tests {
// Temporary directory will be automatically cleaned up when dropped
}

#[tokio::test]
async fn test_http_cache_interface_max_ttl() {
let cache_dir = tempfile::tempdir().unwrap();
let manager = CACacheManager::new(cache_dir.path().to_path_buf(), true);
let cache = HttpCache {
mode: CacheMode::Default,
manager,
options: HttpCacheOptions {
max_ttl: Some(Duration::ZERO),
..HttpCacheOptions::default()
},
};

let response = Response::builder()
.status(StatusCode::OK)
.header("cache-control", "max-age=3600")
.body(b"Hello, world!".to_vec())
.unwrap();

let req = Request::builder()
.method("GET")
.uri("https://example.com/test")
.body(())
.unwrap();
let (parts, _) = req.into_parts();
let analysis = cache.analyze_request(&parts, None).unwrap();

let processed = cache
.process_response(analysis.clone(), response, None)
.await
.unwrap();
assert_eq!(
processed.headers().get("cache-control").unwrap(),
"max-age=3600"
);

let cached =
cache.lookup_cached_response(&analysis.cache_key).await.unwrap();
let (cached_response, policy) = cached.unwrap();
assert_eq!(
cached_response.headers.get("cache-control").map(String::as_str),
Some("max-age=3600")
);
assert_eq!(policy.time_to_live(SystemTime::now()), Duration::ZERO);
assert!(policy.is_stale(SystemTime::now()));
}

#[tokio::test]
async fn test_http_cache_interface_conditional_requests() {
let cache_dir = tempfile::tempdir().unwrap();
Expand Down
Loading