mod common;
use axum::http::StatusCode;
use common::*;
use what_core::server::create_router;
fn extract_session_id(set_cookie: &str) -> &str {
let start = set_cookie.find('=').unwrap() + 1;
let end = set_cookie[start..].find(';').unwrap() + start;
&set_cookie[start..end]
}
#[tokio::test]
async fn new_session_sets_cookie() {
let proj = TestProject::new();
proj.add_page("index.html", "<h1>Home</h1>");
let (_dir, state) = proj.build_state();
let router = create_router(state);
let resp = get(&router, "/").await;
assert_eq!(resp.status, StatusCode::OK);
let cookie = resp.set_cookie().expect("should have Set-Cookie header");
assert!(
cookie.contains("w_session="),
"cookie should contain session id: {}",
cookie
);
}
#[tokio::test]
async fn session_reused_on_subsequent_request() {
let proj = TestProject::new();
proj.add_page("index.html", "<h1>Home</h1>");
let (_dir, state) = proj.build_state();
let router = create_router(state);
let resp1 = get(&router, "/").await;
let cookie_header = resp1.set_cookie().expect("first request should set cookie");
let session_id = extract_session_id(cookie_header);
let cookie_val = format!("w_session={}", session_id);
let resp2 = get_with_headers(&router, "/", vec![("cookie", &cookie_val)]).await;
assert!(
resp2.set_cookie().is_none(),
"second request should not set a new cookie"
);
}
#[tokio::test]
async fn session_variable_reactive_wrapping_in_body() {
let proj = TestProject::new();
proj.add_page(
"index.html",
"<what>\nsession.count += 1\n</what>\n<p>#session.count#</p>",
);
let (_dir, state) = proj.build_state();
let router = create_router(state);
let resp = get(&router, "/").await;
assert_eq!(resp.status, StatusCode::OK);
resp.assert_contains("w-bind=\"session.count\"");
}
#[tokio::test]
async fn session_variable_no_wrapping_in_attributes() {
let proj = TestProject::new();
proj.add_page(
"index.html",
"<what>\nsession.count += 1\n</what>\n<div data-count=\"#session.count#\">Text</div>",
);
let (_dir, state) = proj.build_state();
let router = create_router(state);
let resp = get(&router, "/").await;
assert_eq!(resp.status, StatusCode::OK);
resp.assert_contains("data-count=\"1\"");
resp.assert_not_contains("data-count=\"<span");
}
#[tokio::test]
async fn session_mutation_increment() {
let proj = TestProject::new();
proj.add_page(
"counter.html",
"<what>\nsession.counter += 1\n</what>\n<p>#session.counter#</p>",
);
let (_dir, state) = proj.build_state();
let router = create_router(state);
let resp1 = get(&router, "/counter").await;
assert_eq!(resp1.status, StatusCode::OK);
resp1.assert_contains(">1<");
let cookie_header = resp1.set_cookie().unwrap();
let session_id = extract_session_id(cookie_header);
let cookie_val = format!("w_session={}", session_id);
let resp2 = get_with_headers(&router, "/counter", vec![("cookie", &cookie_val)]).await;
assert_eq!(resp2.status, StatusCode::OK);
resp2.assert_contains(">2<");
}
#[tokio::test]
async fn session_mutation_set() {
let proj = TestProject::new();
proj.add_page(
"hello.html",
"<what>\nsession.name = \"Alice\"\n</what>\n<p>#session.name#</p>",
);
let (_dir, state) = proj.build_state();
let router = create_router(state);
let resp = get(&router, "/hello").await;
assert_eq!(resp.status, StatusCode::OK);
resp.assert_contains("Alice");
}
#[tokio::test]
async fn session_mutation_push() {
let proj = TestProject::new();
proj.add_page(
"items.html",
"<what>\nsession.items.push(\"hello\")\n</what>\n<loop data=\"#session.items#\"><span>#item#</span></loop>",
);
let (_dir, state) = proj.build_state();
let router = create_router(state);
let resp = get(&router, "/items").await;
assert_eq!(resp.status, StatusCode::OK);
resp.assert_contains("hello");
}
#[tokio::test]
async fn session_reset_endpoint() {
let proj = TestProject::new();
proj.add_page("index.html", "<h1>Home</h1>");
let (_dir, state) = proj.build_state();
let router = create_router(state);
let resp1 = get(&router, "/").await;
let cookie_header = resp1.set_cookie().unwrap();
let old_id = extract_session_id(cookie_header);
let cookie_val = format!("w_session={}", old_id);
let csrf = resp1.csrf_token().expect("should have CSRF token");
let resp2 = post_form_with_headers(
&router,
"/w-session/reset",
"redirect=/",
vec![("cookie", &cookie_val), ("X-CSRF-Token", &csrf)],
)
.await;
assert_eq!(resp2.status, StatusCode::SEE_OTHER);
let new_cookie = resp2.set_cookie().expect("reset should set new cookie");
let new_id = extract_session_id(new_cookie);
assert_ne!(old_id, new_id, "reset should create a new session");
}
#[tokio::test]
async fn session_clear_data_endpoint() {
let proj = TestProject::new();
proj.add_page(
"set.html",
"<what>\nsession.val = \"keep\"\n</what>\n<p>#session.val#</p>",
);
let (_dir, state) = proj.build_state();
let router = create_router(state);
let resp1 = get(&router, "/set").await;
let cookie_header = resp1.set_cookie().unwrap();
let session_id = extract_session_id(cookie_header);
let cookie_val = format!("w_session={}", session_id);
let csrf = resp1.csrf_token().expect("should have CSRF token");
let resp2 = post_form_with_headers(
&router,
"/w-session/clear-data",
"",
vec![("cookie", &cookie_val), ("X-CSRF-Token", &csrf)],
)
.await;
assert_eq!(resp2.status, StatusCode::SEE_OTHER);
}
#[tokio::test]
async fn session_mutation_pushmax_caps_array() {
let proj = TestProject::new();
proj.add_page(
"add.html",
"<what>\nsession.log.pushmax(3, \"entry\")\n</what>\n<p>#session.log#</p>",
);
proj.add_page(
"read.html",
"<loop data=\"#session.log#\"><span>#item#</span></loop>",
);
let (_dir, state) = proj.build_state();
let router = create_router(state);
let resp1 = get(&router, "/add").await;
let cookie_header = resp1.set_cookie().unwrap();
let session_id = extract_session_id(cookie_header);
let cookie_val = format!("w_session={}", session_id);
get_with_headers(&router, "/add", vec![("cookie", &cookie_val)]).await;
get_with_headers(&router, "/add", vec![("cookie", &cookie_val)]).await;
get_with_headers(&router, "/add", vec![("cookie", &cookie_val)]).await;
get_with_headers(&router, "/add", vec![("cookie", &cookie_val)]).await;
let resp = get_with_headers(&router, "/read", vec![("cookie", &cookie_val)]).await;
assert_eq!(resp.status, StatusCode::OK);
let count = resp.body.matches("<span>").count();
assert_eq!(
count, 3,
"pushmax(3) should cap array at 3 items, got {}",
count
);
}
#[tokio::test]
async fn session_data_persists_across_requests() {
let proj = TestProject::new();
proj.add_page(
"set.html",
"<what>\nsession.val = \"persisted\"\n</what>\n<p>set</p>",
);
proj.add_page("read.html", "<p>#session.val#</p>");
let (_dir, state) = proj.build_state();
let router = create_router(state);
let resp1 = get(&router, "/set").await;
assert_eq!(resp1.status, StatusCode::OK);
let cookie_header = resp1.set_cookie().unwrap();
let session_id = extract_session_id(cookie_header);
let cookie_val = format!("w_session={}", session_id);
let resp2 = get_with_headers(&router, "/read", vec![("cookie", &cookie_val)]).await;
assert_eq!(resp2.status, StatusCode::OK);
resp2.assert_contains("persisted");
}