#![allow(clippy::unwrap_used, clippy::expect_used, clippy::significant_drop_tightening)]
mod common;
use std::sync::{Arc, Mutex};
use common::TestServer;
use http_body_util::Full;
use hyper::body::Bytes;
use hyper::Response;
#[tokio::test]
async fn cookie_stored_and_sent_on_next_request() {
let received_cookies: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
let cookies_clone = received_cookies.clone();
let server = TestServer::start(move |req| {
if let Some(cookie) = req.headers().get("cookie") {
cookies_clone.lock().unwrap().push(cookie.to_str().unwrap_or("").to_string());
}
if req.uri().path() == "/set" {
Response::builder()
.header("Set-Cookie", "session=abc123")
.body(Full::new(Bytes::from("set")))
.unwrap()
} else {
Response::new(Full::new(Bytes::from("check")))
}
})
.await;
let mut easy = liburlx::Easy::new();
easy.cookie_jar(true);
easy.url(&server.url("/set")).unwrap();
let resp = easy.perform_async().await.unwrap();
assert_eq!(resp.status(), 200);
easy.url(&server.url("/check")).unwrap();
let _resp = easy.perform_async().await.unwrap();
let cookies = received_cookies.lock().unwrap();
assert!(
cookies.iter().any(|c| c.contains("session=abc123")),
"cookie should be sent on second request, got: {cookies:?}"
);
}
#[tokio::test]
async fn multiple_cookies_stored_and_sent() {
let received_cookies: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
let cookies_clone = received_cookies.clone();
let server = TestServer::start(move |req| {
if let Some(cookie) = req.headers().get("cookie") {
cookies_clone.lock().unwrap().push(cookie.to_str().unwrap_or("").to_string());
}
if req.uri().path() == "/set" {
Response::builder()
.header("Set-Cookie", "a=1")
.header("Set-Cookie", "b=2")
.body(Full::new(Bytes::from("set")))
.unwrap()
} else {
Response::new(Full::new(Bytes::from("check")))
}
})
.await;
let mut easy = liburlx::Easy::new();
easy.cookie_jar(true);
easy.url(&server.url("/set")).unwrap();
let _resp = easy.perform_async().await.unwrap();
easy.url(&server.url("/check")).unwrap();
let _resp = easy.perform_async().await.unwrap();
let cookies = received_cookies.lock().unwrap();
let last_cookie = cookies.last().expect("should have received cookies");
assert!(last_cookie.contains("a=1"), "should contain a=1, got: {last_cookie}");
assert!(last_cookie.contains("b=2"), "should contain b=2, got: {last_cookie}");
}
#[tokio::test]
async fn cookie_replaced_on_same_name() {
let received_cookies: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
let cookies_clone = received_cookies.clone();
let call_count = Arc::new(std::sync::atomic::AtomicU32::new(0));
let count_clone = call_count.clone();
let server = TestServer::start(move |req| {
if let Some(cookie) = req.headers().get("cookie") {
cookies_clone.lock().unwrap().push(cookie.to_str().unwrap_or("").to_string());
}
let n = count_clone.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
if n == 0 {
Response::builder()
.header("Set-Cookie", "token=old")
.body(Full::new(Bytes::from("first")))
.unwrap()
} else if n == 1 {
Response::builder()
.header("Set-Cookie", "token=new")
.body(Full::new(Bytes::from("second")))
.unwrap()
} else {
Response::new(Full::new(Bytes::from("third")))
}
})
.await;
let mut easy = liburlx::Easy::new();
easy.cookie_jar(true);
easy.url(&server.url("/")).unwrap();
let _resp = easy.perform_async().await.unwrap();
let _resp = easy.perform_async().await.unwrap();
let _resp = easy.perform_async().await.unwrap();
let cookies = received_cookies.lock().unwrap();
let last_cookie = cookies.last().expect("should have cookies");
assert!(last_cookie.contains("token=new"), "should have new value, got: {last_cookie}");
assert!(!last_cookie.contains("token=old"), "should not have old value");
}
#[tokio::test]
async fn no_cookie_jar_means_no_cookies_sent() {
let received_cookies: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
let cookies_clone = received_cookies.clone();
let server = TestServer::start(move |req| {
if let Some(cookie) = req.headers().get("cookie") {
cookies_clone.lock().unwrap().push(cookie.to_str().unwrap_or("").to_string());
}
Response::builder()
.header("Set-Cookie", "ignored=true")
.body(Full::new(Bytes::from("ok")))
.unwrap()
})
.await;
let mut easy = liburlx::Easy::new();
easy.url(&server.url("/")).unwrap();
let _resp = easy.perform_async().await.unwrap();
let _resp = easy.perform_async().await.unwrap();
let cookies = received_cookies.lock().unwrap();
assert!(cookies.is_empty(), "should not have sent any cookies, got: {cookies:?}");
}
#[tokio::test]
async fn cookie_path_scoping() {
let received_cookies: Arc<Mutex<Vec<(String, String)>>> = Arc::new(Mutex::new(Vec::new()));
let cookies_clone = received_cookies.clone();
let server = TestServer::start(move |req| {
let path = req.uri().path().to_string();
let cookie = req
.headers()
.get("cookie")
.map(|v| v.to_str().unwrap_or("").to_string())
.unwrap_or_default();
cookies_clone.lock().unwrap().push((path.clone(), cookie));
if path == "/api/set" {
Response::builder()
.header("Set-Cookie", "api_token=xyz; Path=/api")
.body(Full::new(Bytes::from("set")))
.unwrap()
} else {
Response::new(Full::new(Bytes::from("check")))
}
})
.await;
let mut easy = liburlx::Easy::new();
easy.cookie_jar(true);
easy.url(&server.url("/api/set")).unwrap();
let _resp = easy.perform_async().await.unwrap();
easy.url(&server.url("/api/data")).unwrap();
let _resp = easy.perform_async().await.unwrap();
easy.url(&server.url("/")).unwrap();
let _resp = easy.perform_async().await.unwrap();
let cookies = received_cookies.lock().unwrap();
let api_request = cookies.iter().find(|(p, _)| p == "/api/data");
assert!(
api_request.is_some() && api_request.unwrap().1.contains("api_token=xyz"),
"cookie should be sent to /api/data"
);
let root_request = cookies.iter().rev().find(|(p, _)| p == "/");
if let Some((_, cookie)) = root_request {
assert!(!cookie.contains("api_token"), "cookie should NOT be sent to /, got: {cookie}");
}
}