mod common;
use axum::http::StatusCode;
use common::*;
use std::time::{SystemTime, UNIX_EPOCH};
use what_core::server::create_router;
#[tokio::test]
async fn directory_without_index_returns_404() {
let proj = TestProject::new();
proj.add_page("index.html", "<h1>Home</h1>");
proj.add_page("blog/post.html", "<h1>A Post</h1>");
let (_dir, state) = proj.build_state();
let router = create_router(state);
let resp = get(&router, "/blog").await;
assert_eq!(resp.status, StatusCode::NOT_FOUND);
}
#[tokio::test]
async fn deep_nonexistent_returns_404() {
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, "/a/b/c/d").await;
assert_eq!(resp.status, StatusCode::NOT_FOUND);
}
#[tokio::test]
async fn exclude_directive_returns_404() {
let proj = TestProject::new();
proj.add_page("index.html", "<h1>Home</h1>");
proj.add_page("hidden.html", "<what exclude />\n<h1>Secret</h1>");
let (_dir, state) = proj.build_state();
let router = create_router(state);
let resp = get(&router, "/hidden").await;
assert_eq!(resp.status, StatusCode::NOT_FOUND);
}
#[tokio::test]
async fn redirect_directive_returns_3xx() {
let proj = TestProject::new();
proj.add_page("index.html", "<h1>Home</h1>");
proj.add_page("old.html", "<what redirect=\"/new-page\" />\n<h1>Old</h1>");
proj.add_page("new-page.html", "<h1>New</h1>");
let (_dir, state) = proj.build_state();
let router = create_router(state);
let resp = get(&router, "/old").await;
assert!(
resp.status.is_redirection(),
"expected redirect, got {}",
resp.status
);
assert_eq!(resp.location(), Some("/new-page"));
}
#[tokio::test]
async fn static_file_serving() {
let proj = TestProject::new();
proj.add_page("index.html", "<h1>Home</h1>");
proj.add_static("style.css", b"body { color: red; }");
let (_dir, state) = proj.build_state();
let router = create_router(state);
let resp = get(&router, "/static/style.css").await;
assert_eq!(resp.status, StatusCode::OK);
resp.assert_contains("body { color: red; }");
}
#[tokio::test]
async fn dev_endpoint_404_in_prod() {
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, "/w-sessions/list").await;
assert_eq!(resp.status, StatusCode::NOT_FOUND);
}
#[tokio::test]
async fn dev_endpoint_200_in_dev() {
let proj = TestProject::new();
proj.add_page("index.html", "<h1>Home</h1>");
let (_dir, state) = proj.build_state_dev(); let router = create_router(state);
let resp = get(&router, "/w-sessions/list").await;
assert_eq!(resp.status, StatusCode::OK);
}
#[tokio::test]
async fn cache_header_production() {
let proj = TestProject::new();
proj.add_page("index.html", "<h1>Home</h1>");
proj.add_static("app.css", b"h1 { font-size: 2em; }");
let (_dir, state) = proj.build_state(); let router = create_router(state);
let resp = get(&router, "/static/app.css").await;
assert_eq!(resp.status, StatusCode::OK);
let cc = resp
.header("cache-control")
.expect("should have cache-control");
assert!(
cc.contains("max-age=3600"),
"production should have max-age=3600, got: {}",
cc
);
}
#[tokio::test]
async fn cache_header_dev() {
let proj = TestProject::new();
proj.add_page("index.html", "<h1>Home</h1>");
proj.add_static("app.css", b"h1 { font-size: 2em; }");
let (_dir, state) = proj.build_state_dev(); let router = create_router(state);
let resp = get(&router, "/static/app.css").await;
assert_eq!(resp.status, StatusCode::OK);
let cc = resp
.header("cache-control")
.expect("should have cache-control");
assert!(
cc.contains("no-cache"),
"dev should have no-cache, got: {}",
cc
);
}
#[tokio::test]
async fn custom_404_page_used() {
let proj = TestProject::new();
proj.add_page("index.html", "<h1>Home</h1>");
proj.add_page("404.html", "<h1>Custom 404 - Page Not Found</h1>");
let (_dir, state) = proj.build_state();
let router = create_router(state);
let resp = get(&router, "/nonexistent-page").await;
assert_eq!(resp.status, StatusCode::NOT_FOUND);
resp.assert_contains("Custom 404 - Page Not Found");
}
#[tokio::test]
async fn custom_404_with_error_context() {
let proj = TestProject::new();
proj.add_page("index.html", "<h1>Home</h1>");
proj.add_page(
"404.html",
"<h1>Error #error.status#</h1><p>Path: #error.path#</p>",
);
let (_dir, state) = proj.build_state();
let router = create_router(state);
let resp = get(&router, "/does-not-exist").await;
assert_eq!(resp.status, StatusCode::NOT_FOUND);
resp.assert_contains("Error 404");
resp.assert_contains("Path: /does-not-exist");
}
#[tokio::test]
async fn custom_403_page_used() {
let proj = TestProject::new();
proj.add_page("index.html", "<h1>Home</h1>");
proj.add_page("secret.html", "<what auth=\"admin\" />\n<h1>Secret</h1>");
proj.add_page(
"403.html",
"<h1>Custom 403 - Access Denied</h1><p>#error.path#</p>",
);
let mut config = what_core::Config::default();
config.auth.enabled = true;
config.auth.jwt_secret = Some("test-secret".to_string());
let (_dir, state) = proj.build_state_with_config(config);
let router = create_router(state);
let exp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
+ 3600;
let claims = serde_json::json!({
"sub": "user1",
"id": "user1",
"email": "user@test.com",
"full_name": "Test User",
"exp": exp
});
let token = create_test_jwt(&claims, "test-secret");
let resp = get_with_headers(
&router,
"/secret",
vec![("cookie", &format!("w_token={}", token))],
)
.await;
assert_eq!(resp.status, StatusCode::FORBIDDEN);
resp.assert_contains("Custom 403 - Access Denied");
resp.assert_contains("/secret");
}
#[tokio::test]
async fn error_message_hidden_in_production() {
let proj = TestProject::new();
proj.add_page("index.html", "<h1>Home</h1>");
proj.add_page(
"404.html",
"<h1>#error.status#</h1><p>Detail: #error.message#</p>",
);
let (_dir, state) = proj.build_state(); let router = create_router(state);
let resp = get(&router, "/missing").await;
assert_eq!(resp.status, StatusCode::NOT_FOUND);
resp.assert_contains("Detail: </p>"); }
#[tokio::test]
async fn error_message_shown_in_dev() {
let proj = TestProject::new();
proj.add_page("index.html", "<h1>Home</h1>");
proj.add_page("404.html", "<h1>#error.status#</h1><p>#error.message#</p>");
let (_dir, state) = proj.build_state_dev(); let router = create_router(state);
let resp = get(&router, "/missing").await;
assert_eq!(resp.status, StatusCode::NOT_FOUND);
resp.assert_contains("Page not found");
}