mod common; use axum::http::StatusCode; use common::{ delete_authed, get, get_authed, patch_json_authed, post_json_authed, response_body, setup_app, setup_app_with_auth, }; use tower::ServiceExt; // GET /api/v1/users (admin) #[tokio::test] async fn list_users_requires_admin() { let (app, _, editor_token, _) = setup_app_with_auth().await; let response = app .oneshot(get_authed("/api/v1/users", &editor_token)) .await .unwrap(); assert_eq!(response.status(), StatusCode::FORBIDDEN); } #[tokio::test] async fn list_users_viewer_forbidden() { let (app, _, _, viewer_token) = setup_app_with_auth().await; let response = app .oneshot(get_authed("/api/v1/users", &viewer_token)) .await .unwrap(); assert_eq!(response.status(), StatusCode::FORBIDDEN); } #[tokio::test] async fn list_users_admin_ok() { let (app, admin_token, ..) = setup_app_with_auth().await; let response = app .oneshot(get_authed("/api/v1/users", &admin_token)) .await .unwrap(); let status = response.status(); let body = response_body(response).await; assert_eq!(status, StatusCode::OK); let users = body.as_array().expect("users is array"); // setup_app_with_auth seeds three users assert_eq!(users.len(), 3); } // POST /api/v1/users (admin) #[tokio::test] async fn create_user_requires_admin() { let (app, _, editor_token, _) = setup_app_with_auth().await; let response = app .oneshot(post_json_authed( "/api/v1/users", r#"{"username":"newuser","password":"password123","role":"viewer"}"#, &editor_token, )) .await .unwrap(); assert_eq!(response.status(), StatusCode::FORBIDDEN); } #[tokio::test] async fn create_user_admin_ok() { let (app, admin_token, ..) = setup_app_with_auth().await; let response = app .oneshot(post_json_authed( "/api/v1/users", r#"{"username":"newuser","password":"password123","role":"viewer"}"#, &admin_token, )) .await .unwrap(); let status = response.status(); let body = response_body(response).await; assert!( status == StatusCode::OK || status == StatusCode::CREATED, "unexpected status: {status}" ); assert!(body["id"].is_string(), "expected id field, got: {body}"); assert_eq!(body["username"].as_str().unwrap(), "newuser"); } #[tokio::test] async fn create_user_duplicate_username() { let (app, admin_token, ..) = setup_app_with_auth().await; // "admin" already exists let response = app .oneshot(post_json_authed( "/api/v1/users", r#"{"username":"admin","password":"password123","role":"viewer"}"#, &admin_token, )) .await .unwrap(); assert_eq!(response.status(), StatusCode::CONFLICT); } #[tokio::test] async fn create_user_password_too_short() { let (app, admin_token, ..) = setup_app_with_auth().await; let response = app .oneshot(post_json_authed( "/api/v1/users", r#"{"username":"shortpass","password":"short","role":"viewer"}"#, &admin_token, )) .await .unwrap(); // Password minimum is 8 chars; should be rejected assert!( response.status() == StatusCode::BAD_REQUEST || response.status() == StatusCode::UNPROCESSABLE_ENTITY ); } // GET /api/v1/users/{id} (admin) #[tokio::test] async fn get_user_requires_admin() { let (app, _, _, viewer_token) = setup_app_with_auth().await; let response = app .oneshot(get_authed( "/api/v1/users/00000000-0000-0000-0000-000000000000", &viewer_token, )) .await .unwrap(); assert_eq!(response.status(), StatusCode::FORBIDDEN); } #[tokio::test] async fn get_user_not_found() { let (app, admin_token, ..) = setup_app_with_auth().await; let response = app .oneshot(get_authed( "/api/v1/users/00000000-0000-0000-0000-000000000000", &admin_token, )) .await .unwrap(); assert_eq!(response.status(), StatusCode::NOT_FOUND); } // PATCH /api/v1/users/{id} (admin) #[tokio::test] async fn update_user_requires_admin() { let (app, _, _, viewer_token) = setup_app_with_auth().await; let response = app .oneshot(patch_json_authed( "/api/v1/users/00000000-0000-0000-0000-000000000000", r#"{"role":"editor"}"#, &viewer_token, )) .await .unwrap(); assert_eq!(response.status(), StatusCode::FORBIDDEN); } // DELETE /api/v1/users/{id} (admin) #[tokio::test] async fn delete_user_requires_admin() { let (app, _, _, viewer_token) = setup_app_with_auth().await; let response = app .oneshot(delete_authed( "/api/v1/users/00000000-0000-0000-0000-000000000000", &viewer_token, )) .await .unwrap(); assert_eq!(response.status(), StatusCode::FORBIDDEN); } #[tokio::test] async fn delete_user_not_found() { let (app, admin_token, ..) = setup_app_with_auth().await; let response = app .oneshot(delete_authed( "/api/v1/users/00000000-0000-0000-0000-000000000000", &admin_token, )) .await .unwrap(); assert_eq!(response.status(), StatusCode::NOT_FOUND); } // GET /api/v1/users/{id}/libraries (admin) #[tokio::test] async fn get_user_libraries_requires_admin() { let (app, _, editor_token, _) = setup_app_with_auth().await; let response = app .oneshot(get_authed( "/api/v1/users/00000000-0000-0000-0000-000000000000/libraries", &editor_token, )) .await .unwrap(); assert_eq!(response.status(), StatusCode::FORBIDDEN); } // No-auth coverage (exercises setup_app and get helpers) #[tokio::test] async fn media_list_no_auth_users_file() { let app = setup_app().await; let response = app.oneshot(get("/api/v1/media")).await.unwrap(); assert_eq!(response.status(), StatusCode::OK); }