This commit is contained in:
Mateusz Gruszczyński committed 2026-10-04 11:15:58 +02:00
1 parent 87e571c170
commit fa7abc9e53
308 files changed
+49746 -50237

No files matched your search

+170
View File
@@ -0,0 +1,170 @@
#[tokio::test]
async fn opaque_cookie_is_removed_from_origin_requests_and_protected_on_responses() {
let pool = pool(Algorithm::RoundRobin, StickyMode::Cookie);
let first = select(&pool);
let cookie = first.cookie.unwrap().to_str().unwrap().to_owned();
assert!(
cookie.contains("HttpOnly")
&& cookie.contains("SameSite=Lax")
&& !cookie.contains("Secure")
);
let cookie = cookie.split(';').next().unwrap();
let mut headers = HeaderMap::new();
headers.insert("cookie", format!("{cookie}; app=ok").parse().unwrap());
let next = pool
.select("10.0.0.1".parse().unwrap(), &headers, false)
.unwrap();
assert_eq!(
next.lease.member.definition.id,
first.lease.member.definition.id
);
assert!(next.cookie.is_none());
pool.strip_affinity(&mut headers);
assert_eq!(headers["cookie"], "app=ok");
headers.append(
"set-cookie",
format!("{}=origin-spoof", pool.cookie_name)
.parse()
.unwrap(),
);
headers.append("set-cookie", "app=ok".parse().unwrap());
pool.strip_affinity_response(&mut headers);
assert_eq!(headers.get_all("set-cookie").iter().count(), 1);
assert_eq!(headers["set-cookie"], "app=ok");
let secure = pool
.select("127.0.0.1".parse().unwrap(), &HeaderMap::new(), true)
.unwrap();
assert!(secure
.cookie
.unwrap()
.to_str()
.unwrap()
.contains("; Secure"));
}
#[tokio::test]
async fn failed_sticky_member_is_replaced_and_duplicate_cookies_are_ignored() {
let pool = pool(Algorithm::RoundRobin, StickyMode::Cookie);
let first = select(&pool);
let cookie = first
.cookie
.unwrap()
.to_str()
.unwrap()
.split(';')
.next()
.unwrap()
.to_owned();
let mut headers = HeaderMap::new();
headers.insert("cookie", format!("{cookie}; {cookie}").parse().unwrap());
assert!(pool.affinity(&headers).is_none());
headers.insert("cookie", cookie.parse().unwrap());
lock(&first.lease.member.state.health).healthy = false;
let next = pool
.select("127.0.0.1".parse().unwrap(), &headers, false)
.unwrap();
assert_ne!(
next.lease.member.definition.id,
first.lease.member.definition.id
);
assert!(next.cookie.is_some());
lock(&next.lease.member.state.health).healthy = false;
assert!(pool
.select("127.0.0.1".parse().unwrap(), &headers, false)
.is_none());
}
#[tokio::test]
async fn slow_start_ramps_weight_and_error_is_counted_once_per_exchange() {
let pool = pool(Algorithm::LeastConnections, StickyMode::Off);
let selected = select(&pool);
let state = &selected.lease.member.state;
assert!(state.weight(100, 100) < 100.0);
assert_eq!(state.weight(100, 0), 100.0);
selected.lease.started();
selected.lease.bytes(50, true);
selected.lease.bytes(100, false);
selected.lease.error("timeout", Duration::from_secs(1));
selected.lease.error("body", Duration::from_secs(2));
selected.lease.retry_attempted();
selected.lease.retry_succeeded();
assert_eq!(state.requests.load(Ordering::Relaxed), 1);
assert_eq!(state.errors.load(Ordering::Relaxed), 1);
assert_eq!(state.timeouts_total.load(Ordering::Relaxed), 1);
assert_eq!(state.io_errors_total.load(Ordering::Relaxed), 1);
assert_eq!(state.bytes_in.load(Ordering::Relaxed), 50);
assert_eq!(state.bytes_out.load(Ordering::Relaxed), 100);
assert_eq!(state.retry_attempts_total.load(Ordering::Relaxed), 1);
assert_eq!(state.retry_successes_total.load(Ordering::Relaxed), 1);
assert!(pool
.prometheus()
.contains("rust_proxy_backend_requests_total{vhost=\"app\",backend=\"a\"} 1"));
assert!(pool
.prometheus()
.contains("rust_proxy_backend_retry_attempts_total{vhost=\"app\",backend=\"a\"} 1"));
}
#[tokio::test]
async fn slow_5xx_counts_both_observability_classes_when_passive_is_off() {
let pool = pool(Algorithm::LeastConnections, StickyMode::Off);
let selected = select(&pool);
selected.lease.response(500, Duration::from_secs(20), true);
let state = &selected.lease.member.state;
assert_eq!(state.http_5xx_total.load(Ordering::Relaxed), 1);
assert_eq!(state.slow_responses_total.load(Ordering::Relaxed), 1);
assert_eq!(state.passive_health_failures_total.load(Ordering::Relaxed), 0);
assert!(state.healthy());
}
#[tokio::test]
async fn client_upload_duration_does_not_trigger_passive_slow_failure() {
let pool = pool(Algorithm::LeastConnections, StickyMode::Off);
let upload = select(&pool);
upload.lease.response(200, Duration::from_secs(20), false);
assert_eq!(upload.lease.member.state.errors.load(Ordering::Relaxed), 0);
let read = select(&pool);
read.lease.response(200, Duration::from_secs(20), true);
assert_eq!(read.lease.member.state.errors.load(Ordering::Relaxed), 1);
}
#[tokio::test]
async fn backup_is_only_new_assignment_when_no_primary_is_eligible() {
let mut pool = pool(Algorithm::RoundRobin, StickyMode::Off);
Arc::get_mut(&mut Arc::get_mut(&mut pool).unwrap().members[1])
.unwrap()
.definition
.backup = true;
for _ in 0..8 {
assert_eq!(select(&pool).lease.member.definition.id, "a");
}
lock(&pool.members[0].state.health).healthy = false;
assert_eq!(select(&pool).lease.member.definition.id, "b");
lock(&pool.members[0].state.health).healthy = true;
assert_eq!(select(&pool).lease.member.definition.id, "a");
}
#[tokio::test]
async fn drain_rejects_new_sessions_but_preserves_valid_existing_affinity() {
let mut pool = pool(Algorithm::RoundRobin, StickyMode::Cookie);
Arc::get_mut(&mut Arc::get_mut(&mut pool).unwrap().members[0])
.unwrap()
.definition
.drain = true;
assert_eq!(select(&pool).lease.member.definition.id, "b");
let mut headers = HeaderMap::new();
headers.insert(
"cookie",
format!("{}={}", pool.cookie_name, pool.members[0].token)
.parse()
.unwrap(),
);
let existing = pool
.select("127.0.0.1".parse().unwrap(), &headers, false)
.unwrap();
assert_eq!(existing.lease.member.definition.id, "a");
lock(&pool.members[0].state.health).healthy = false;
assert_eq!(
pool.select("127.0.0.1".parse().unwrap(), &headers, false)
.unwrap()
.lease
.member
.definition
.id,
"b"
);
}