mod common; use axum::body::Body; use axum::http::{Request, StatusCode, header}; use tower::util::ServiceExt; use common::{ TestConfig, body_string, extract_session_cookie, get_with_cookie, login_request, paths, test_user_with_password, }; #[tokio::test] async fn login_success_sets_session_cookie_and_redirects() { let cfg = TestConfig::new() .with_user(test_user_with_password("dr_a", "secret")) .build(); let app = doctate_server::create_router(cfg); let resp = app.oneshot(login_request("dr_a", "secret")).await.unwrap(); assert_eq!( resp.status(), StatusCode::SEE_OTHER.as_u16(), "got {:?}", resp.status() ); let loc = resp .headers() .get(header::LOCATION) .unwrap() .to_str() .unwrap(); assert_eq!(loc, "/web/cases"); let cookie = extract_session_cookie(&resp).expect("no session cookie"); assert!(cookie.starts_with("session=")); assert!( cookie.len() > "session=".len() + 20, "token looks too short: {cookie}" ); } #[tokio::test] async fn login_wrong_password_shows_error() { let cfg = TestConfig::new() .with_user(test_user_with_password("dr_a", "secret")) .build(); let app = doctate_server::create_router(cfg); let resp = app.oneshot(login_request("dr_a", "wrong")).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); assert!(extract_session_cookie(&resp).is_none()); let body = body_string(resp).await; assert!(body.contains("Login fehlgeschlagen"), "got: {body}"); } #[tokio::test] async fn login_unknown_user_shows_error() { let cfg = TestConfig::new() .with_user(test_user_with_password("dr_a", "secret")) .build(); let app = doctate_server::create_router(cfg); let resp = app .oneshot(login_request("ghost", "anything")) .await .unwrap(); assert_eq!(resp.status(), StatusCode::OK); assert!(extract_session_cookie(&resp).is_none()); } #[tokio::test] async fn cases_without_cookie_redirects_to_login() { let cfg = TestConfig::new() .with_user(test_user_with_password("dr_a", "secret")) .build(); let app = doctate_server::create_router(cfg); let resp = app .oneshot( Request::builder() .uri(paths::CASES) .body(Body::empty()) .unwrap(), ) .await .unwrap(); assert_eq!(resp.status(), StatusCode::FOUND); assert_eq!( resp.headers() .get(header::LOCATION) .unwrap() .to_str() .unwrap(), paths::LOGIN ); } #[tokio::test] async fn cases_with_valid_cookie_shows_only_own_cases() { let cfg = TestConfig::new() .with_user(test_user_with_password("dr_a", "s")) .with_user(test_user_with_password("dr_b", "s")) .build(); // Seed fixtures: dr_a has an open case, dr_b has one too. let case_a = cfg .data_path .join("dr_a/11111111-1111-1111-1111-111111111111"); let case_b = cfg .data_path .join("dr_b/22222222-2222-2222-2222-222222222222"); std::fs::create_dir_all(&case_a).unwrap(); std::fs::create_dir_all(&case_b).unwrap(); std::fs::write(case_a.join("2026-04-14T10-00-00Z.m4a"), b"x").unwrap(); std::fs::write(case_b.join("2026-04-14T11-00-00Z.m4a"), b"x").unwrap(); let app = doctate_server::create_router(cfg); let cookie = common::login(&app, "dr_a", "s").await; let resp = app .oneshot(get_with_cookie(paths::CASES, &cookie)) .await .unwrap(); assert_eq!(resp.status(), StatusCode::OK); let body = body_string(resp).await; assert!(body.contains("11111111"), "own case missing: {body}"); assert!(!body.contains("22222222"), "foreign case leaked: {body}"); } #[tokio::test] async fn case_detail_of_foreign_case_returns_404() { let cfg = TestConfig::new() .with_user(test_user_with_password("dr_a", "s")) .with_user(test_user_with_password("dr_b", "s")) .build(); let case_b = cfg .data_path .join("dr_b/22222222-2222-2222-2222-222222222222"); std::fs::create_dir_all(&case_b).unwrap(); std::fs::write(case_b.join("2026-04-14T11-00-00Z.m4a"), b"x").unwrap(); let app = doctate_server::create_router(cfg); let cookie = common::login(&app, "dr_a", "s").await; let resp = app .oneshot(get_with_cookie( paths::case_detail("22222222-2222-2222-2222-222222222222"), &cookie, )) .await .unwrap(); assert_eq!(resp.status(), StatusCode::NOT_FOUND); } #[tokio::test] async fn logout_clears_session() { let cfg = TestConfig::new() .with_user(test_user_with_password("dr_a", "s")) .build(); let (app, store) = doctate_server::create_router_and_session_store(cfg); let (cookie, csrf) = common::login_with_csrf(&app, &store, "dr_a", "s").await; let logout = app .clone() .oneshot(common::csrf_form_post(paths::LOGOUT, &cookie, &csrf, "")) .await .unwrap(); assert_eq!(logout.status(), StatusCode::SEE_OTHER.as_u16()); // Same cookie must no longer be accepted. let resp = app .oneshot(get_with_cookie(paths::CASES, &cookie)) .await .unwrap(); assert_eq!(resp.status(), StatusCode::FOUND); }