use httpmock::{Method::GET, Method::POST, Mock, MockServer};
use reqwest::header;
mod common;
use goose::config::GooseConfiguration;
use goose::prelude::*;
struct SessionData(String);
const SESSION_DATA: &str = "This is my session data.";
const SESSION_PATH: &str = "/session";
const COOKIE_PATH: &str = "/cookie";
const POST_SESSION_KEY: usize = 0;
const GET_SESSION_KEY: usize = 1;
const POST_COOKIE_KEY_0: usize = 2;
const GET_COOKIE_KEY_0: usize = 6;
const POST_COOKIE_KEY_1: usize = 7;
const GET_COOKIE_KEY_1: usize = 11;
const POST_COOKIE_KEY_2: usize = 12;
const GET_COOKIE_KEY_2: usize = 16;
const POST_COOKIE_KEY_3: usize = 17;
const GET_COOKIE_KEY_3: usize = 21;
const SESSION_USERS: &str = "10";
const COOKIE_USERS: &str = "4";
#[derive(Clone)]
enum TestType {
Session,
Cookie,
}
pub async fn set_session_data(user: &mut GooseUser) -> TransactionResult {
let session_data = user.get_session_data::<SessionData>();
assert!(session_data.is_none());
let _goose = user.post(SESSION_PATH, SESSION_DATA).await?;
user.set_session_data(SessionData(format!(
"{}.{}",
SESSION_DATA, user.weighted_users_index
)));
let session_data = user.get_session_data::<SessionData>();
assert!(session_data.is_some());
Ok(())
}
pub async fn validate_session_data(user: &mut GooseUser) -> TransactionResult {
let _goose = user.get(SESSION_PATH).await?;
let session_data = user.get_session_data::<SessionData>();
assert!(session_data.is_some());
if let Some(data) = session_data {
assert!(data.0 == format!("{}.{}", SESSION_DATA, user.weighted_users_index));
} else {
panic!("no session data !?");
}
Ok(())
}
pub async fn set_cookie(user: &mut GooseUser) -> TransactionResult {
let cookie_name = format!("TestCookie{}", user.weighted_users_index);
let cookie_path = format!("{}{}", COOKIE_PATH, user.weighted_users_index);
let request_builder = user
.get_request_builder(&GooseMethod::Post, &cookie_path)?
.header("Cookie", format!("{cookie_name}=foo"));
let goose_request = GooseRequest::builder()
.set_request_builder(request_builder)
.build();
let goose = user.request(goose_request).await?;
let response = goose.response.expect("there must be a response");
let cookie: reqwest::cookie::Cookie = response.cookies().next().expect("cookie must be set");
assert!(cookie.name() == cookie_name);
Ok(())
}
pub async fn validate_cookie(user: &mut GooseUser) -> TransactionResult {
let cookie_path = format!("{}{}", COOKIE_PATH, user.weighted_users_index);
let _goose = user.get(&cookie_path).await?;
Ok(())
}
fn setup_mock_server_endpoints(server: &MockServer) -> Vec<Mock<'_>> {
let cookie_path_0 = format!("{COOKIE_PATH}0");
let cookie_path_1 = format!("{COOKIE_PATH}1");
let cookie_path_2 = format!("{COOKIE_PATH}2");
let cookie_path_3 = format!("{COOKIE_PATH}3");
vec![
server.mock(|when, then| {
when.method(POST).path(SESSION_PATH);
then.status(200);
}),
server.mock(|when, then| {
when.method(GET).path(SESSION_PATH);
then.status(200);
}),
server.mock(|when, then| {
when.method(POST).path(&cookie_path_0);
then.status(200)
.header(header::SET_COOKIE.as_str(), "TestCookie0=foo");
}),
server.mock(|when, then| {
when.method(GET)
.path(&cookie_path_0)
.cookie_exists("TestCookie1");
then.status(500);
}),
server.mock(|when, then| {
when.method(GET)
.path(&cookie_path_0)
.cookie_exists("TestCookie2");
then.status(500);
}),
server.mock(|when, then| {
when.method(GET)
.path(&cookie_path_0)
.cookie_exists("TestCookie3");
then.status(500);
}),
server.mock(|when, then| {
when.method(GET)
.path(cookie_path_0)
.cookie_exists("TestCookie0");
then.status(200);
}),
server.mock(|when, then| {
when.method(POST).path(&cookie_path_1);
then.status(200)
.header(header::SET_COOKIE.as_str(), "TestCookie1=foo");
}),
server.mock(|when, then| {
when.method(GET)
.path(&cookie_path_1)
.cookie_exists("TestCookie0");
then.status(500);
}),
server.mock(|when, then| {
when.method(GET)
.path(&cookie_path_1)
.cookie_exists("TestCookie2");
then.status(500);
}),
server.mock(|when, then| {
when.method(GET)
.path(&cookie_path_1)
.cookie_exists("TestCookie3");
then.status(500);
}),
server.mock(|when, then| {
when.method(GET)
.path(cookie_path_1)
.cookie_exists("TestCookie1");
then.status(200);
}),
server.mock(|when, then| {
when.method(POST).path(&cookie_path_2);
then.status(200)
.header(header::SET_COOKIE.as_str(), "TestCookie2=foo");
}),
server.mock(|when, then| {
when.method(GET)
.path(&cookie_path_2)
.cookie_exists("TestCookie0");
then.status(500);
}),
server.mock(|when, then| {
when.method(GET)
.path(&cookie_path_2)
.cookie_exists("TestCookie1");
then.status(500);
}),
server.mock(|when, then| {
when.method(GET)
.path(&cookie_path_2)
.cookie_exists("TestCookie3");
then.status(500);
}),
server.mock(|when, then| {
when.method(GET)
.path(cookie_path_2)
.cookie_exists("TestCookie2");
then.status(200);
}),
server.mock(|when, then| {
when.method(POST).path(&cookie_path_3);
then.status(200)
.header(header::SET_COOKIE.as_str(), "TestCookie3=foo");
}),
server.mock(|when, then| {
when.method(GET)
.path(&cookie_path_3)
.cookie_exists("TestCookie0");
then.status(500);
}),
server.mock(|when, then| {
when.method(GET)
.path(&cookie_path_3)
.cookie_exists("TestCookie1");
then.status(500);
}),
server.mock(|when, then| {
when.method(GET)
.path(&cookie_path_3)
.cookie_exists("TestCookie2");
then.status(500);
}),
server.mock(|when, then| {
when.method(GET)
.path(cookie_path_3)
.cookie_exists("TestCookie3");
then.status(200);
}),
]
}
fn common_build_configuration(
test_type: &TestType,
server: &MockServer,
custom: &mut Vec<&str>,
) -> GooseConfiguration {
let mut configuration = match test_type {
TestType::Session => vec![
"--users",
SESSION_USERS,
"--hatch-rate",
SESSION_USERS,
"--run-time",
"2",
],
TestType::Cookie => vec![
"--users",
COOKIE_USERS,
"--hatch-rate",
COOKIE_USERS,
"--run-time",
"2",
],
};
configuration.append(custom);
common::build_configuration(server, configuration)
}
fn validate_requests(test_type: TestType, goose_metrics: &GooseMetrics, mock_endpoints: &[Mock]) {
let users = match test_type {
TestType::Session => SESSION_USERS
.parse::<usize>()
.expect("must be a valid usize"),
TestType::Cookie => COOKIE_USERS
.parse::<usize>()
.expect("must be a valid usize"),
};
match test_type {
TestType::Session => {
assert!(mock_endpoints[POST_SESSION_KEY].hits() == users);
assert!(mock_endpoints[GET_SESSION_KEY].hits() > users);
}
TestType::Cookie => {
assert!(mock_endpoints[POST_COOKIE_KEY_0].hits() == 1);
assert!(mock_endpoints[POST_COOKIE_KEY_1].hits() == 1);
assert!(mock_endpoints[POST_COOKIE_KEY_2].hits() == 1);
assert!(mock_endpoints[POST_COOKIE_KEY_3].hits() == 1);
assert!(mock_endpoints[GET_COOKIE_KEY_0].hits() > 1);
assert!(mock_endpoints[GET_COOKIE_KEY_1].hits() > 1);
assert!(mock_endpoints[GET_COOKIE_KEY_2].hits() > 1);
assert!(mock_endpoints[GET_COOKIE_KEY_3].hits() > 1);
}
}
let post_metrics = match test_type {
TestType::Session => goose_metrics.requests.get("POST create session").unwrap(),
TestType::Cookie => goose_metrics.requests.get("POST create cookie").unwrap(),
};
let get_metrics = match test_type {
TestType::Session => goose_metrics.requests.get("GET read session").unwrap(),
TestType::Cookie => goose_metrics.requests.get("GET read cookie").unwrap(),
};
assert!(post_metrics.method == GooseMethod::Post);
assert!(get_metrics.method == GooseMethod::Get);
assert!(post_metrics.success_count == users);
assert!(get_metrics.success_count > users);
assert!(post_metrics.fail_count == 0);
assert!(get_metrics.fail_count == 0);
}
fn get_scenarios(test_type: &TestType) -> Scenario {
match test_type {
TestType::Session => {
scenario!("Sessions")
.register_transaction(
transaction!(set_session_data)
.set_on_start()
.set_name("create session"),
)
.register_transaction(transaction!(validate_session_data).set_name("read session"))
}
TestType::Cookie => {
scenario!("Cookie")
.register_transaction(
transaction!(set_cookie)
.set_on_start()
.set_name("create cookie"),
)
.register_transaction(transaction!(validate_cookie).set_name("read cookie"))
}
}
}
async fn run_standalone_test(test_type: TestType) {
let server = MockServer::start();
let mock_endpoints = setup_mock_server_endpoints(&server);
let mut configuration_flags = vec!["--no-reset-metrics"];
let configuration = common_build_configuration(&test_type, &server, &mut configuration_flags);
let goose_metrics = common::run_load_test(
common::build_load_test(
configuration.clone(),
vec![get_scenarios(&test_type)],
None,
None,
),
None,
)
.await;
validate_requests(test_type, &goose_metrics, &mock_endpoints);
}
#[tokio::test]
async fn test_session() {
run_standalone_test(TestType::Session).await;
}
#[tokio::test]
async fn test_cookie() {
run_standalone_test(TestType::Cookie).await;
}