#[cfg(test)] mod cache_revision_tests { use super::*; #[test] fn operational_saves_keep_cache_identity_but_cache_affecting_saves_rotate_it() { let base = config::demo_config("127.0.0.1:9000".parse().unwrap()); let identity = cache_revision(&base); let mut file_transport = base.clone(); file_transport.web_services.push(crate::web_server::Service { id: "static-test".into(), name: "Static Test".into(), document_root: "/var/www/static-test".into(), ..Default::default() }); let mut zero_copy = file_transport.clone(); zero_copy.web_services[0].sendfile = crate::web_server::SendfileMode::Auto; assert_eq!(cache_revision(&file_transport), cache_revision(&zero_copy)); assert!(matches!(cache_invalidation(&file_transport, &zero_copy), CacheInvalidation::None)); let mut logging = base.clone(); logging.logging.file.path = "/tmp/rustproxy-test-access.jsonl".into(); assert_eq!(cache_revision(&logging), identity); assert!(matches!( cache_invalidation(&base, &logging), CacheInvalidation::None )); let mut metrics = base.clone(); metrics.prometheus.include_system = !metrics.prometheus.include_system; assert_eq!(cache_revision(&metrics), identity); let mut response = base.clone(); response.server_header.value = "edge-test".into(); response.diagnostic_headers.request_id = !response.diagnostic_headers.request_id; response.compression.gzip = !response.compression.gzip; assert_eq!(cache_revision(&response), identity); let mut docker = base.clone(); docker.docker_agents.agents.push(crate::docker::Agent { id: "node1".into(), name: "Node 1".into(), ..Default::default() }); assert_eq!(cache_revision(&docker), identity); let mut preset = base.clone(); preset.cache.memory_preset = Some(crate::cache::MemoryPreset::Recommended); preset.cache.cold_preset = Some(crate::cache::ColdPreset::Recommended); preset.transform.preset = Some(crate::transform::Preset::Uber); preset.transform.memory_budget_bytes = 1024 * 1024 * 1024; preset.transform.max_inflight = 128; assert_eq!(cache_revision(&preset), identity); assert!(matches!( cache_invalidation(&base, &preset), CacheInvalidation::None )); let mut transform_policy = base.clone(); transform_policy.transform.json = !transform_policy.transform.json; assert_ne!(cache_revision(&transform_policy), identity); let mut transform_body_limit = base.clone(); transform_body_limit.transform.max_body_bytes += 1024; assert_ne!(cache_revision(&transform_body_limit), identity); let mut wire_only = base.clone(); wire_only.cache.bypass_query = !wire_only.cache.bypass_query; wire_only.cache.bypass_prefixes.push("/private".into()); wire_only.cache.expose_status = !wire_only.cache.expose_status; wire_only.cache.expose_hits = !wire_only.cache.expose_hits; wire_only.cache.expose_cache_status = !wire_only.cache.expose_cache_status; wire_only.cache.expose_resident_age = !wire_only.cache.expose_resident_age; wire_only.cache.expose_only_hits = !wire_only.cache.expose_only_hits; wire_only.cache.via_header = Some("RustProxy edge".into()); wire_only.cache.http_invalidation_allowed_cidrs = vec!["192.0.2.42".into()]; assert_eq!(cache_revision(&wire_only), identity); assert!(matches!( cache_invalidation(&base, &wire_only), CacheInvalidation::None )); for label in ["http2", "http3"] { let mut protocol_only = base.clone(); if label == "http2" { protocol_only.vhosts[0].http2_enabled = !base.vhosts[0].http2_enabled; } else { protocol_only.vhosts[0].http3_enabled = !base.vhosts[0].http3_enabled; } // The effective configuration really changes, but the cache key // does not. This must hold for both toggling directions. assert_ne!( (protocol_only.vhosts[0].http2_enabled, protocol_only.vhosts[0].http3_enabled), (base.vhosts[0].http2_enabled, base.vhosts[0].http3_enabled), "{label} toggle did not change a transport switch" ); assert_eq!(cache_revision(&protocol_only), identity, "{label} rotated cache"); assert!(matches!(cache_invalidation(&base, &protocol_only), CacheInvalidation::None)); assert!(matches!(cache_invalidation(&protocol_only, &base), CacheInvalidation::None)); } let mut both_protocols = base.clone(); both_protocols.vhosts[0].http2_enabled = !base.vhosts[0].http2_enabled; both_protocols.vhosts[0].http3_enabled = !base.vhosts[0].http3_enabled; assert_eq!(cache_revision(&both_protocols), identity); assert!(matches!(cache_invalidation(&base, &both_protocols), CacheInvalidation::None)); let mut vhost_diagnostics = base.clone(); vhost_diagnostics.vhosts[0].cache.expose_only_hits = Some(true); assert_eq!(cache_revision(&vhost_diagnostics), identity); assert!(matches!(cache_invalidation(&base, &vhost_diagnostics), CacheInvalidation::None)); let mut cache = base.clone(); cache.cache.max_entries += 1; let cache_identity = cache_revision(&cache); assert_ne!(cache_identity, identity); assert!(matches!( cache_invalidation(&base, &cache), CacheInvalidation::All(value) if value == cache_identity )); let mut operational = base.clone(); operational.vhosts[0].load_balancing.keepalive.enabled = false; operational.vhosts[0].load_balancing.keepalive.idle_timeout_seconds = 90; operational.vhosts[0].load_balancing.keepalive.max_idle_per_host = 32; operational.vhosts[0].load_balancing.keepalive.tcp_keepalive_seconds = 90; operational.vhosts[0].load_balancing.keepalive.pool_recycle_requests = 1_000; operational.vhosts[0].load_balancing.keepalive.pool_recycle_seconds = 1_800; operational.vhosts[0].load_balancing.keepalive.http1_max_buffer_bytes = 65_536; operational.vhosts[0].backends[0].weight += 1; operational.vhosts[0].backends[0].max_concurrent_requests = 16; operational.vhosts[0].backends[0].queue_timeout_ms = 100; operational.vhosts[0].timing_headers = true; operational.vhosts[0].acl_profile_id = Some("test-only-acl".into()); operational.vhosts[0].user_agent_blocking = Default::default(); operational.vhosts[0].proxy_timeouts.read_timeout_seconds = Some(180); assert_eq!(cache_revision(&operational), identity); assert_eq!(vhost_cache_revision(&operational.vhosts[0]), vhost_cache_revision(&base.vhosts[0])); assert!(matches!(cache_invalidation(&base, &operational), CacheInvalidation::None)); assert!(matches!(cache_invalidation(&operational, &base), CacheInvalidation::None)); let mut vhost = base.clone(); vhost.vhosts[0].backends[0].url = "http://127.0.0.1:9001".into(); assert_eq!(cache_revision(&vhost), identity); let affected_id = base.vhosts[0].id.clone(); assert!(matches!(cache_invalidation(&base, &vhost), CacheInvalidation::Scopes(ids) if ids == vec![affected_id])); // A content change must not invalidate another independent vhost. let mut multiple = base.clone(); let mut another = multiple.vhosts[0].clone(); another.id = "other-scope".into(); another.frontend.hosts = vec!["other.example.test".into()]; multiple.vhosts.push(another); let mut changed_one = multiple.clone(); changed_one.vhosts[0].backends[0].url = "http://127.0.0.1:9002".into(); assert!(matches!(cache_invalidation(&multiple, &changed_one), CacheInvalidation::Scopes(ids) if ids == vec![base.vhosts[0].id.clone()])); let mut changed_global = multiple.clone(); changed_global.transform.max_body_bytes += 1; assert!(matches!(cache_invalidation(&multiple, &changed_global), CacheInvalidation::All(_))); } }