mod cold_tier_tests { use super::*; use http_body_util::BodyExt; fn cold_settings() -> Settings { Settings { enabled: true, max_entries: 1, max_bytes: 256 * 1024, max_object_bytes: 64 * 1024, cold_enabled: true, cold_path: "cache/cold-test".into(), cold_max_bytes: 2 * 1024 * 1024, cold_max_entries: 16, cold_after_seconds: 300, ..Settings::default() } } async fn fill(cache: &Arc, settings: &Settings, path: &str, body: &'static str) -> Ticket { let ticket = cache .ticket_scoped( 11, "cold-test", "http", "cold.example", "origin@127.0.0.1:8080", path, &HeaderMap::new(), ) .unwrap(); let mut response = Response::new(Body::from(body)); response.headers_mut().insert( "cache-control", HeaderValue::from_static("public, max-age=120"), ); response .headers_mut() .insert("etag", HeaderValue::from_static("\"cold-v1\"")); let plan = plan(StatusCode::OK, response.headers(), settings, Duration::ZERO).unwrap(); cache .wrap(response, ticket.clone(), plan, settings) .into_body() .collect() .await .unwrap(); ticket } async fn wait_for_cold(cache: &Cache, expected: usize) { for _ in 0..100 { if cache.view().cold_entries >= expected { return; } tokio::time::sleep(Duration::from_millis(10)).await; } panic!("cold tier did not persist in time"); } #[tokio::test] async fn selective_purge_removes_matching_cold_object() { let dir = tempfile::tempdir().unwrap(); let settings = cold_settings(); let shutdown = CancellationToken::new(); let cache = Cache::start( 11, Some(dir.path().to_path_buf()), settings.clone(), shutdown.clone(), ); let first = fill(&cache, &settings, "/one?v=1", "first-payload").await; let _second = fill(&cache, &settings, "/two", "second-payload").await; wait_for_cold(&cache, 1).await; assert_eq!(cache.view().cold_entries, 1); let result = cache .invalidate(InvalidationRequest { operation: InvalidationOperation::Purge, selector: InvalidationSelector::Exact, value: "/one?v=1".into(), host: Some("cold.example".into()), vhost: Some("cold-test".into()), ttl_seconds: None, confirm_wide: false, }) .unwrap(); assert_eq!(result.affected_entries, 1); assert_eq!(cache.view().cold_entries, 0); assert!(cache.get_tiered(&first, &settings).await.is_none()); shutdown.cancel(); } #[tokio::test] async fn aged_transformed_css_remains_fresh_when_demoted_to_cold() { let dir = tempfile::tempdir().unwrap(); let settings = cold_settings(); let shutdown = CancellationToken::new(); let cache = Cache::start(11, Some(dir.path().to_path_buf()), settings.clone(), shutdown.clone()); let ticket = cache.ticket_scoped( 11, "cold-test", "http", "cold.example", "origin@127.0.0.1:8080", "/aged-theme.css", &HeaderMap::new(), ).unwrap(); let body = b"body { color: red; margin: 0px; }"; let mut origin = Response::new(Body::from(Bytes::from_static(body))); origin.headers_mut().insert("cache-control", HeaderValue::from_static("public, max-age=2592000")); origin.headers_mut().insert("age", HeaderValue::from_static("1553483")); origin.headers_mut().insert("date", httpdate::fmt_http_date(SystemTime::now()).parse().unwrap()); origin.headers_mut().insert("content-type", HeaderValue::from_static("text/css")); origin.headers_mut().insert("content-length", HeaderValue::from_str(&body.len().to_string()).unwrap()); origin.headers_mut().insert("etag", HeaderValue::from_static("\"origin\"")); let origin_plan = plan(StatusCode::OK, origin.headers(), &settings, Duration::ZERO).unwrap(); let transformed = crate::transform::apply(origin, "/aged-theme.css", &crate::transform::Settings { enabled: true, css: true, ..Default::default() }, false ).await.unwrap(); let transformed_plan = plan(StatusCode::OK, transformed.headers(), &settings, Duration::ZERO).unwrap(); cache.wrap(transformed, ticket.clone(), origin_plan.restrict(transformed_plan), &settings) .into_body().collect().await.unwrap(); assert_eq!(cache.view().entries, 1); let _another = fill(&cache, &settings, "/another", "another-payload").await; wait_for_cold(&cache, 1).await; let row = cache.dashboard(Some("cold-test"), 0, 20).objects .into_iter().find(|v| v.path == "/aged-theme.css").unwrap(); assert_eq!(row.tier, "cold"); assert!(row.transformed); assert!(row.remaining_ttl_seconds >= 86_000); shutdown.cancel(); drop(cache); let shutdown = CancellationToken::new(); let cache = Cache::start(11, Some(dir.path().to_path_buf()), settings.clone(), shutdown.clone()); let hit = cache.get_tiered(&ticket, &settings).await.expect("aged transformed COLD should HIT after restart"); assert!(hit.transformed); assert!(hit.remaining_ttl >= 86_000); let transformed_body = hit.response.into_body().collect().await.unwrap().to_bytes(); assert_ne!(transformed_body.as_ref(), &body[..]); shutdown.cancel(); } #[tokio::test] async fn lru_demotion_persists_and_survives_restart() { let dir = tempfile::tempdir().unwrap(); let settings = cold_settings(); settings.validate().unwrap(); let shutdown = CancellationToken::new(); let cache = Cache::start( 11, Some(dir.path().to_path_buf()), settings.clone(), shutdown.clone(), ); let first = fill(&cache, &settings, "/one", "first-payload").await; let _second = fill(&cache, &settings, "/two", "second-payload").await; wait_for_cold(&cache, 1).await; assert_eq!(cache.view().entries, 1); assert_eq!(cache.view().cold_entries, 1); let cold = cache.dashboard(Some("cold-test"), 0, 50); assert!(cold .objects .iter() .any(|row| row.path == "/one" && row.tier == "cold")); shutdown.cancel(); drop(cache); let restart_shutdown = CancellationToken::new(); let restarted = Cache::start( 11, Some(dir.path().to_path_buf()), settings.clone(), restart_shutdown.clone(), ); assert_eq!(restarted.view().cold_entries, 1); let hit = restarted .get_tiered(&first, &settings) .await .expect("cold object should be readable after restart"); let payload = hit.response.into_body().collect().await.unwrap().to_bytes(); assert_eq!(payload, Bytes::from_static(b"first-payload")); assert_eq!(restarted.view().cold_hits, 1); assert_eq!(restarted.view().cold_promotions, 1); restart_shutdown.cancel(); } #[tokio::test] async fn dashboard_lists_retained_expired_cold_objects_without_permitting_fresh_hits() { let dir = tempfile::tempdir().unwrap(); let settings = cold_settings(); let shutdown = CancellationToken::new(); let cache = Cache::start(11, Some(dir.path().to_path_buf()), settings.clone(), shutdown.clone()); let first = fill(&cache, &settings, "/one", "first-payload").await; fill(&cache, &settings, "/two", "second-payload").await; wait_for_cold(&cache, 1).await; { let mut cold = cache.cold.lock().unwrap(); for entry in cold.entries.values_mut() { entry.stored_at = SystemTime::now() - Duration::from_secs(2 * 60 * 60); } } let dashboard = cache.dashboard(Some("cold-test"), 0, 50); assert_eq!(dashboard.matching_objects, 2); let row = dashboard.objects.iter().find(|row| row.path == "/one").unwrap(); assert_eq!(row.tier, "cold"); assert_eq!(row.remaining_ttl_seconds, 0); assert!(row.age_seconds >= 2 * 60 * 60); assert_eq!(row.storage_bytes, cache.view().cold_bytes); assert!(cache.get_tiered(&first, &settings).await.is_none()); let mut stale = first; stale.request_headers.insert("cache-control", HeaderValue::from_static("max-stale")); let hit = cache.get_tiered(&stale, &settings).await.expect("explicit max-stale can reuse the retained disk object"); assert!(hit.stale); assert_eq!(hit.response.into_body().collect().await.unwrap().to_bytes(), "first-payload"); shutdown.cancel(); } #[tokio::test] async fn cold_restart_preserves_transformed_validators_stale_metadata_and_no_transform() { let dir = tempfile::tempdir().unwrap(); let mut settings = cold_settings(); settings.max_entries = 8; let shutdown = CancellationToken::new(); let cache = Cache::start( 11, Some(dir.path().to_path_buf()), settings.clone(), shutdown.clone(), ); let transformed_ticket = cache .ticket_scoped( 11, "cold-test", "http", "cold.example", "origin@127.0.0.1:8080", "/transformed.json", &HeaderMap::new(), ) .unwrap(); let mut transformed = Response::new(Body::from(r#"{"a":1}"#)); transformed.headers_mut().insert( "cache-control", HeaderValue::from_static( "public, max-age=120, stale-while-revalidate=30, stale-if-error=90", ), ); transformed.headers_mut().insert( "etag", HeaderValue::from_static("\"rustproxy-representation\""), ); transformed .headers_mut() .insert("content-length", HeaderValue::from_static("7")); transformed .extensions_mut() .insert(crate::transform::OriginValidators { etag: Some(HeaderValue::from_static("\"origin-v1\"")), last_modified: Some(HeaderValue::from_static("Mon, 26 Jan 2026 23:11:40 GMT")), }); transformed .extensions_mut() .insert(crate::transform::Applied { kind: crate::transform::Kind::Json, original_bytes: 9, transformed_bytes: 7, }); let transformed_plan = plan( StatusCode::OK, transformed.headers(), &settings, Duration::ZERO, ) .unwrap(); cache .wrap( transformed, transformed_ticket.clone(), transformed_plan, &settings, ) .into_body() .collect() .await .unwrap(); let protected_ticket = cache .ticket_scoped( 11, "cold-test", "http", "cold.example", "origin@127.0.0.1:8080", "/protected.css", &HeaderMap::new(), ) .unwrap(); let original_body = "body { color: red; }"; let mut protected = Response::new(Body::from(original_body)); protected.headers_mut().insert( "cache-control", HeaderValue::from_static("public, max-age=120, immutable, no-transform"), ); protected .headers_mut() .insert("etag", HeaderValue::from_static("\"origin-css\"")); protected.extensions_mut().insert(crate::compression::Skip); let protected_plan = plan( StatusCode::OK, protected.headers(), &settings, Duration::ZERO, ) .unwrap(); cache .wrap( protected, protected_ticket.clone(), protected_plan, &settings, ) .into_body() .collect() .await .unwrap(); assert_eq!(cache.flush_hot_to_cold().await, 2); wait_for_cold(&cache, 2).await; shutdown.cancel(); drop(cache); let restart_shutdown = CancellationToken::new(); let restarted = Cache::start( 11, Some(dir.path().to_path_buf()), settings.clone(), restart_shutdown.clone(), ); assert_eq!(restarted.view().cold_entries, 2); // COLD entries intentionally keep validators and headers on disk to bound RAM. // Inspect them through the same verified metadata loader used on COLD hits. let (key, transformed) = { let cold = restarted.cold.lock().unwrap(); cold.entries .iter() .find(|(_, entry)| entry.path == "/transformed.json") .map(|(key, entry)| (*key, entry.clone())) .expect("transformed cold entry") }; assert_eq!(transformed.stale_while_revalidate, 30); assert_eq!(transformed.stale_if_error, 90); assert!(transformed.has_validator); assert!(transformed.transformed); let metadata = read_cold_metadata(&key, &transformed) .await .expect("persisted transformed metadata"); assert_eq!(metadata.origin_etag.as_ref().unwrap(), "\"origin-v1\""); assert_eq!( metadata.origin_last_modified.as_ref().unwrap(), "Mon, 26 Jan 2026 23:11:40 GMT" ); assert_eq!(metadata.headers["etag"], "\"rustproxy-representation\""); let transformed_hit = restarted .get_tiered(&transformed_ticket, &settings) .await .expect("transformed object after restart"); assert_eq!( transformed_hit.response.headers()["etag"], "\"rustproxy-representation\"" ); assert!(transformed_hit.transformed); assert!(transformed_hit .response .extensions() .get::() .is_some()); assert_eq!( transformed_hit .response .into_body() .collect() .await .unwrap() .to_bytes(), Bytes::from_static(br#"{"a":1}"#) ); let protected_hit = restarted .get_tiered(&protected_ticket, &settings) .await .expect("no-transform object after restart"); assert_eq!(protected_hit.response.headers()["etag"], "\"origin-css\""); assert!(crate::transform::no_transform( protected_hit.response.headers() )); assert!(protected_hit .response .extensions() .get::() .is_some()); assert_eq!( protected_hit .response .into_body() .collect() .await .unwrap() .to_bytes(), Bytes::from_static(b"body { color: red; }") ); restart_shutdown.cancel(); } #[tokio::test] async fn cold_policy_can_disable_lru_demotion() { let dir = tempfile::tempdir().unwrap(); let mut settings = cold_settings(); settings.cold_on_lru = false; let shutdown = CancellationToken::new(); let cache = Cache::start( 11, Some(dir.path().to_path_buf()), settings.clone(), shutdown.clone(), ); fill(&cache, &settings, "/one", "first-payload").await; fill(&cache, &settings, "/two", "second-payload").await; tokio::time::sleep(Duration::from_millis(30)).await; let view = cache.view(); assert_eq!(view.entries, 1); assert_eq!(view.cold_entries, 0); assert_eq!(view.evictions, 1); shutdown.cancel(); } #[tokio::test] async fn cold_policy_can_disable_idle_demotion() { let dir = tempfile::tempdir().unwrap(); let mut settings = cold_settings(); settings.max_entries = 8; settings.cold_on_idle = false; let shutdown = CancellationToken::new(); let cache = Cache::start( 11, Some(dir.path().to_path_buf()), settings.clone(), shutdown.clone(), ); fill(&cache, &settings, "/idle", "idle-payload").await; { let mut store = cache.store.lock().unwrap(); for entry in store.entries.values_mut() { entry.last_access = Instant::now() - Duration::from_secs(600); } } cache.demote_idle(); assert_eq!(cache.view().entries, 1); assert_eq!(cache.view().cold_entries, 0); settings.cold_on_idle = true; cache.configure(settings.clone()); cache.demote_idle(); wait_for_cold(&cache, 1).await; assert_eq!(cache.view().entries, 0); shutdown.cancel(); } #[tokio::test] async fn cold_policy_can_demote_by_total_hot_residency_age() { let dir = tempfile::tempdir().unwrap(); let mut settings = cold_settings(); settings.max_entries = 8; settings.cold_on_idle = false; settings.cold_on_age = true; settings.cold_max_hot_seconds = 5; let shutdown = CancellationToken::new(); let cache = Cache::start( 11, Some(dir.path().to_path_buf()), settings.clone(), shutdown.clone(), ); fill(&cache, &settings, "/aged", "aged-payload").await; { let mut store = cache.store.lock().unwrap(); for entry in store.entries.values_mut() { entry.resident_since = Instant::now() - Duration::from_secs(60); entry.last_access = Instant::now(); } } cache.demote_idle(); wait_for_cold(&cache, 1).await; assert_eq!(cache.view().entries, 0); assert_eq!(cache.view().cold_entries, 1); shutdown.cancel(); } #[tokio::test] async fn graceful_shutdown_flush_moves_hot_objects_and_releases_resident_bytes() { let dir = tempfile::tempdir().unwrap(); let mut settings = cold_settings(); settings.max_entries = 8; let shutdown = CancellationToken::new(); let cache = Cache::start( 11, Some(dir.path().to_path_buf()), settings.clone(), shutdown.clone(), ); fill(&cache, &settings, "/one", "first-payload").await; fill(&cache, &settings, "/two", "second-payload").await; assert_eq!(cache.view().entries, 2); assert!(cache.view().resident_bytes > 0); assert_eq!(cache.view().cold_entries, 0); assert_eq!(cache.flush_hot_to_cold().await, 2); let view = cache.view(); assert_eq!(view.entries, 0); assert_eq!(view.resident_bytes, 0); assert_eq!(view.hot_logical_bytes, 0); assert_eq!( view.transient_bytes, 0, "persisted ColdEntry must not retain the body allocation" ); assert_eq!(view.cold_entries, 2); shutdown.cancel(); let restart_shutdown = CancellationToken::new(); let restarted = Cache::start( 11, Some(dir.path().to_path_buf()), settings.clone(), restart_shutdown.clone(), ); assert_eq!(restarted.view().cold_entries, 2); restart_shutdown.cancel(); } #[tokio::test] async fn managed_update_flush_ignores_shutdown_toggle_when_cold_tier_is_enabled() { let dir = tempfile::tempdir().unwrap(); let mut settings = cold_settings(); settings.max_entries = 8; settings.cold_on_shutdown = false; let shutdown = CancellationToken::new(); let cache = Cache::start( 11, Some(dir.path().to_path_buf()), settings.clone(), shutdown.clone(), ); fill(&cache, &settings, "/update", "update-payload").await; assert_eq!(cache.flush_hot_to_cold().await, 0); assert_eq!(cache.view().entries, 1); assert_eq!(cache.flush_hot_to_cold_for_update().await, 1); let view = cache.view(); assert_eq!(view.entries, 0); assert_eq!(view.resident_bytes, 0); assert_eq!(view.cold_entries, 1); shutdown.cancel(); } #[tokio::test] async fn graceful_shutdown_flush_can_be_disabled() { let dir = tempfile::tempdir().unwrap(); let mut settings = cold_settings(); settings.max_entries = 8; settings.cold_on_shutdown = false; let shutdown = CancellationToken::new(); let cache = Cache::start( 11, Some(dir.path().to_path_buf()), settings.clone(), shutdown.clone(), ); fill(&cache, &settings, "/one", "first-payload").await; assert_eq!(cache.flush_hot_to_cold().await, 0); assert_eq!(cache.view().entries, 1); assert_eq!(cache.view().cold_entries, 0); shutdown.cancel(); } #[tokio::test] async fn cold_cache_uses_two_hex_character_shards() { let dir = tempfile::tempdir().unwrap(); let settings = cold_settings(); let shutdown = CancellationToken::new(); let cache = Cache::start( 11, Some(dir.path().to_path_buf()), settings.clone(), shutdown.clone(), ); fill(&cache, &settings, "/one", "first-payload").await; fill(&cache, &settings, "/two", "second-payload").await; wait_for_cold(&cache, 1).await; let root = dir.path().join(&settings.cold_path); let mut shards = Vec::new(); for item in std::fs::read_dir(&root).unwrap() { let item = item.unwrap(); let name = item.file_name().to_string_lossy().to_string(); assert!( item.path().is_dir(), "cold root must contain shard directories only" ); assert_eq!(name.len(), 2); assert!(name.bytes().all(|b| b.is_ascii_hexdigit())); shards.push(item.path()); } assert!(!shards.is_empty()); assert!(shards.iter().any(|shard| { std::fs::read_dir(shard) .unwrap() .filter_map(|entry| entry.ok()) .any(|entry| { let name = entry.file_name().to_string_lossy().to_string(); name.ends_with(".json") || name.ends_with(".body") }) })); shutdown.cancel(); } #[tokio::test] async fn legacy_flat_cold_cache_is_not_migrated() { let dir = tempfile::tempdir().unwrap(); let settings = cold_settings(); let root = dir.path().join(&settings.cold_path); std::fs::create_dir_all(&root).unwrap(); std::fs::write(root.join("legacy.body"), b"old").unwrap(); std::fs::write(root.join("legacy.json"), b"{}").unwrap(); let shutdown = CancellationToken::new(); let cache = Cache::start( 11, Some(dir.path().to_path_buf()), settings, shutdown.clone(), ); assert_eq!(cache.view().cold_entries, 0); assert!(!root.join("legacy.body").exists()); assert!(!root.join("legacy.json").exists()); shutdown.cancel(); } #[tokio::test] async fn manual_cold_cleanup_reports_removed_objects_and_freed_bytes() { let dir = tempfile::tempdir().unwrap(); let mut settings = cold_settings(); settings.max_entries = 1; let shutdown = CancellationToken::new(); let cache = Cache::start( 11, Some(dir.path().to_path_buf()), settings.clone(), shutdown.clone(), ); fill(&cache, &settings, "/one", "first-payload").await; fill(&cache, &settings, "/two", "second-payload").await; fill(&cache, &settings, "/three", "third-payload").await; wait_for_cold(&cache, 2).await; let job = cache .start_cold_cleanup(ColdCleanupRequest { unused_for_seconds: None, oldest: Some(1), hits_zero: false, }) .unwrap(); for _ in 0..100 { let current = cache.cold_cleanup_job(job.id).unwrap(); if current.status == "done" { assert_eq!(current.removed_entries, 1); assert!(current.freed_bytes > 0); assert_eq!(cache.view().cold_entries, 1); shutdown.cancel(); return; } tokio::time::sleep(Duration::from_millis(10)).await; } panic!("cold cleanup did not finish"); } #[tokio::test] async fn manual_cold_cleanup_is_single_flight() { let dir = tempfile::tempdir().unwrap(); let settings = cold_settings(); let shutdown = CancellationToken::new(); let cache = Cache::start( 11, Some(dir.path().to_path_buf()), settings, shutdown.clone(), ); let _held = cache.cleanup_slots.clone().try_acquire_owned().unwrap(); let result = cache.start_cold_cleanup(ColdCleanupRequest { unused_for_seconds: None, oldest: Some(1), hits_zero: false, }); assert!(result.is_err()); shutdown.cancel(); } #[tokio::test] async fn cold_cleanup_skips_an_entry_while_a_read_is_pinned() { let dir = tempfile::tempdir().unwrap(); let mut settings = cold_settings(); settings.max_entries = 1; let shutdown = CancellationToken::new(); let cache = Cache::start( 11, Some(dir.path().to_path_buf()), settings.clone(), shutdown.clone(), ); fill(&cache, &settings, "/one", "first-payload").await; fill(&cache, &settings, "/two", "second-payload").await; wait_for_cold(&cache, 1).await; let key = { let mut cold = cache.cold.lock().unwrap(); let key = *cold.entries.keys().next().unwrap(); cold.entries.get_mut(&key).unwrap().pinned_reads = 1; key }; let request = ColdCleanupRequest { unused_for_seconds: None, oldest: Some(1), hits_zero: false, }; let result = cache .remove_cold_candidates(Some(&request), None, None, 1) .await; assert_eq!(result.removed_entries, 0); assert!(cache.cold.lock().unwrap().entries.contains_key(&key)); cache .cold .lock() .unwrap() .entries .get_mut(&key) .unwrap() .pinned_reads = 0; let result = cache .remove_cold_candidates(Some(&request), None, None, 1) .await; assert_eq!(result.removed_entries, 1); assert!(!cache.cold.lock().unwrap().entries.contains_key(&key)); shutdown.cancel(); } #[test] fn old_cold_read_guard_does_not_unpin_a_replacement_with_the_same_key() { let cache = Cache::new(1); let key = [7_u8; 32]; let template = ColdEntry { base_key: key, vary: Vec::new(), revision: 1, scope: "app".to_owned(), scheme: "https".to_owned(), authority: "example.test".to_owned(), domain: "example.test".to_owned(), path: "/asset".to_owned(), target: "https://example.test/asset".to_owned(), tags: Vec::new(), created_at: SystemTime::now(), body_path: PathBuf::from("old.body"), meta_path: PathBuf::from("old.json"), body_bytes: 1, body_sha256: [0u8; 32], disk_bytes: 2, stored_at: SystemTime::now(), last_access: SystemTime::now(), age_at_store_seconds: 0, resident_age_at_store_seconds: 0, ttl: 60, stale_while_revalidate: 0, stale_if_error: 0, must_revalidate: false, proxy_revalidate: false, has_validator: false, hits: 0, transformed: false, sequence: 1, generation: 1, skip_compression: false, requires_revalidation: false, pinned_reads: 1, }; cache .cold .lock() .unwrap() .entries .insert(key, template.clone()); let old_guard = ColdReadGuard { cache: &cache, key, generation: 1, }; let mut replacement = template; replacement.sequence = 2; replacement.generation = 2; replacement.pinned_reads = 1; cache.cold.lock().unwrap().entries.insert(key, replacement); drop(old_guard); let cold = cache.cold.lock().unwrap(); let replacement = cold.entries.get(&key).unwrap(); assert_eq!(replacement.generation, 2); assert_eq!(replacement.pinned_reads, 1); } #[test] fn cold_path_cannot_escape_data_directory() { let mut settings = Settings::default(); settings.cold_path = "../outside".into(); assert!(settings.validate().is_err()); settings.cold_path = "/tmp/outside".into(); assert!(settings.validate().is_err()); settings.cold_path = "cache/cold".into(); assert!(settings.validate().is_ok()); } #[tokio::test] async fn cold_scope_fingerprint_preserves_unmodified_vhost_and_rejects_stale_restart() { let dir = tempfile::tempdir().unwrap(); let settings = cold_settings(); let shutdown = CancellationToken::new(); let mut fingerprints = HashMap::new(); fingerprints.insert("cold-test".to_owned(), 77); let cache = Cache::start_with_scope_revisions( 11, Some(dir.path().to_path_buf()), settings.clone(), shutdown.clone(), fingerprints.clone(), ); fill(&cache, &settings, "/one", "stable-contents").await; fill(&cache, &settings, "/two", "second-contents").await; wait_for_cold(&cache, 1).await; shutdown.cancel(); drop(cache); let restart = CancellationToken::new(); let same = Cache::start_with_scope_revisions( 11, Some(dir.path().to_path_buf()), settings.clone(), restart.clone(), fingerprints.clone(), ); assert_eq!(same.view().cold_entries, 1, "operational saves retain COLD"); restart.cancel(); drop(same); fingerprints.insert("cold-test".to_owned(), 78); let restart = CancellationToken::new(); let changed = Cache::start_with_scope_revisions( 11, Some(dir.path().to_path_buf()), settings.clone(), restart.clone(), fingerprints, ); assert_eq!(changed.view().cold_entries, 0, "old COLD must never survive a content revision"); restart.cancel(); } }