diff --git a/src/test_utils.rs b/src/test_utils.rs index c1d86d6..391a44c 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -19,6 +19,8 @@ use testcontainers::{runners::AsyncRunner, ContainerAsync, ImageExt}; use testcontainers_modules::postgres::Postgres; #[cfg(any(test, feature = "test-utils"))] use tower::util::ServiceExt; +#[cfg(any(test, feature = "test-utils"))] +use uuid; /// Test image information with expected OCR content #[derive(Debug, Clone)] @@ -221,9 +223,14 @@ pub async fn create_test_app() -> (Router, ContainerAsync) { #[cfg(any(test, feature = "test-utils"))] pub async fn create_test_user(app: &Router) -> UserResponse { + // Generate random identifiers to avoid test interference + let test_id = uuid::Uuid::new_v4().to_string()[..8].to_string(); + let test_username = format!("testuser_{}", test_id); + let test_email = format!("test_{}@example.com", test_id); + let user_data = json!({ - "username": "testuser", - "email": "test@example.com", + "username": test_username, + "email": test_email, "password": "password123" }); @@ -248,9 +255,14 @@ pub async fn create_test_user(app: &Router) -> UserResponse { #[cfg(any(test, feature = "test-utils"))] pub async fn create_admin_user(app: &Router) -> UserResponse { + // Generate random identifiers to avoid test interference + let test_id = uuid::Uuid::new_v4().to_string()[..8].to_string(); + let admin_username = format!("adminuser_{}", test_id); + let admin_email = format!("admin_{}@example.com", test_id); + let admin_data = json!({ - "username": "adminuser", - "email": "admin@example.com", + "username": admin_username, + "email": admin_email, "password": "adminpass123", "role": "admin" }); diff --git a/src/tests/config_oidc_tests.rs b/src/tests/config_oidc_tests.rs index d6eaff2..e5a1b5c 100644 --- a/src/tests/config_oidc_tests.rs +++ b/src/tests/config_oidc_tests.rs @@ -120,6 +120,15 @@ mod tests { #[test] fn test_oidc_partial_config() { + // Clean up environment first to ensure test isolation + env::remove_var("OIDC_ENABLED"); + env::remove_var("OIDC_CLIENT_ID"); + env::remove_var("OIDC_CLIENT_SECRET"); + env::remove_var("OIDC_ISSUER_URL"); + env::remove_var("OIDC_REDIRECT_URI"); + env::remove_var("DATABASE_URL"); + env::remove_var("JWT_SECRET"); + // Only set some OIDC vars env::set_var("OIDC_ENABLED", "true"); env::set_var("OIDC_CLIENT_ID", "test-client-id"); @@ -138,12 +147,24 @@ mod tests { // Clean up env::remove_var("OIDC_ENABLED"); env::remove_var("OIDC_CLIENT_ID"); + env::remove_var("OIDC_CLIENT_SECRET"); + env::remove_var("OIDC_ISSUER_URL"); + env::remove_var("OIDC_REDIRECT_URI"); env::remove_var("DATABASE_URL"); env::remove_var("JWT_SECRET"); } #[test] fn test_oidc_disabled_with_config_present() { + // Clean up environment first to ensure test isolation + env::remove_var("OIDC_ENABLED"); + env::remove_var("OIDC_CLIENT_ID"); + env::remove_var("OIDC_CLIENT_SECRET"); + env::remove_var("OIDC_ISSUER_URL"); + env::remove_var("OIDC_REDIRECT_URI"); + env::remove_var("DATABASE_URL"); + env::remove_var("JWT_SECRET"); + // OIDC disabled but config present env::set_var("OIDC_ENABLED", "false"); env::set_var("OIDC_CLIENT_ID", "test-client-id"); @@ -173,6 +194,15 @@ mod tests { #[test] fn test_oidc_empty_values() { + // Clean up environment first to ensure test isolation + env::remove_var("OIDC_ENABLED"); + env::remove_var("OIDC_CLIENT_ID"); + env::remove_var("OIDC_CLIENT_SECRET"); + env::remove_var("OIDC_ISSUER_URL"); + env::remove_var("OIDC_REDIRECT_URI"); + env::remove_var("DATABASE_URL"); + env::remove_var("JWT_SECRET"); + env::set_var("OIDC_ENABLED", "true"); env::set_var("OIDC_CLIENT_ID", ""); env::set_var("OIDC_CLIENT_SECRET", ""); @@ -202,6 +232,15 @@ mod tests { #[test] fn test_oidc_config_validation_output() { + // Clean up environment first to ensure test isolation + env::remove_var("OIDC_ENABLED"); + env::remove_var("OIDC_CLIENT_ID"); + env::remove_var("OIDC_CLIENT_SECRET"); + env::remove_var("OIDC_ISSUER_URL"); + env::remove_var("OIDC_REDIRECT_URI"); + env::remove_var("DATABASE_URL"); + env::remove_var("JWT_SECRET"); + // Test that validation warnings are properly formatted env::set_var("OIDC_ENABLED", "true"); env::set_var("DATABASE_URL", "postgresql://test:test@localhost/test"); @@ -215,12 +254,25 @@ mod tests { // Clean up env::remove_var("OIDC_ENABLED"); + env::remove_var("OIDC_CLIENT_ID"); + env::remove_var("OIDC_CLIENT_SECRET"); + env::remove_var("OIDC_ISSUER_URL"); + env::remove_var("OIDC_REDIRECT_URI"); env::remove_var("DATABASE_URL"); env::remove_var("JWT_SECRET"); } #[test] fn test_oidc_complete_configuration() { + // Clean up environment first to ensure test isolation + env::remove_var("OIDC_ENABLED"); + env::remove_var("OIDC_CLIENT_ID"); + env::remove_var("OIDC_CLIENT_SECRET"); + env::remove_var("OIDC_ISSUER_URL"); + env::remove_var("OIDC_REDIRECT_URI"); + env::remove_var("DATABASE_URL"); + env::remove_var("JWT_SECRET"); + env::set_var("OIDC_ENABLED", "true"); env::set_var("OIDC_CLIENT_ID", "my-app-client-id"); env::set_var("OIDC_CLIENT_SECRET", "super-secret-client-secret"); diff --git a/src/tests/oidc_tests.rs b/src/tests/oidc_tests.rs index e088bfd..9d40fce 100644 --- a/src/tests/oidc_tests.rs +++ b/src/tests/oidc_tests.rs @@ -7,6 +7,7 @@ mod tests { use wiremock::{matchers::{method, path, query_param, header}, Mock, MockServer, ResponseTemplate}; use std::sync::Arc; use crate::{AppState, oidc::OidcClient}; + use uuid; async fn create_test_app_simple() -> (axum::Router, ()) { // Use TEST_DATABASE_URL directly, no containers @@ -239,6 +240,12 @@ mod tests { async fn test_oidc_callback_success_new_user() { let (app, mock_server) = create_test_app_with_oidc().await; + // Generate random identifiers to avoid test interference + let test_id = uuid::Uuid::new_v4().to_string()[..8].to_string(); + let test_username = format!("oidcuser_{}", test_id); + let test_email = format!("oidc_{}@example.com", test_id); + let test_subject = format!("oidc-user-{}", test_id); + // Clean up any existing test user to ensure test isolation let database_url = std::env::var("TEST_DATABASE_URL") .or_else(|_| std::env::var("DATABASE_URL")) @@ -247,8 +254,8 @@ mod tests { // Delete any existing user with the test username or OIDC subject let _ = sqlx::query("DELETE FROM users WHERE username = $1 OR oidc_subject = $2") - .bind("oidcuser") - .bind("oidc-user-123") + .bind(&test_username) + .bind(&test_subject) .execute(&db.pool) .await; @@ -271,10 +278,10 @@ mod tests { // Mock user info let user_info_response = json!({ - "sub": "oidc-user-123", - "email": "oidc@example.com", + "sub": test_subject, + "email": test_email, "name": "OIDC User", - "preferred_username": "oidcuser" + "preferred_username": test_username }); Mock::given(method("GET")) @@ -327,8 +334,8 @@ mod tests { let login_response: serde_json::Value = serde_json::from_slice(&body).unwrap(); assert!(login_response["token"].is_string()); - assert_eq!(login_response["user"]["username"], "oidcuser"); - assert_eq!(login_response["user"]["email"], "oidc@example.com"); + assert_eq!(login_response["user"]["username"], test_username); + assert_eq!(login_response["user"]["email"], test_email); } #[tokio::test] @@ -362,6 +369,11 @@ mod tests { async fn test_oidc_callback_invalid_user_info() { let (app, mock_server) = create_test_app_with_oidc().await; + // Generate random identifiers to avoid test interference + let test_id = uuid::Uuid::new_v4().to_string()[..8].to_string(); + let test_username = format!("oidcuser_{}", test_id); + let test_subject = format!("oidc-user-{}", test_id); + // Clean up any existing test user to ensure test isolation let database_url = std::env::var("TEST_DATABASE_URL") .or_else(|_| std::env::var("DATABASE_URL")) @@ -369,7 +381,9 @@ mod tests { let db = crate::db::Database::new(&database_url).await.unwrap(); // Delete any existing user that might conflict - let _ = sqlx::query("DELETE FROM users WHERE username LIKE 'oidc%' OR oidc_subject IS NOT NULL") + let _ = sqlx::query("DELETE FROM users WHERE username = $1 OR oidc_subject = $2") + .bind(&test_username) + .bind(&test_subject) .execute(&db.pool) .await; diff --git a/src/tests/users_tests.rs b/src/tests/users_tests.rs index 529f024..369a03e 100644 --- a/src/tests/users_tests.rs +++ b/src/tests/users_tests.rs @@ -5,6 +5,7 @@ mod tests { use axum::http::StatusCode; use serde_json::json; use tower::util::ServiceExt; + use uuid; #[tokio::test] async fn test_list_users() { @@ -302,77 +303,93 @@ mod tests { // OIDC Database Tests #[tokio::test] async fn test_create_oidc_user() { - let (app, container) = create_test_app().await; + let (_app, container) = create_test_app().await; let port = container.get_host_port_ipv4(5432).await.unwrap(); let database_url = format!("postgresql://test:test@localhost:{}/test", port); let db = crate::db::Database::new(&database_url).await.unwrap(); + // Generate random identifiers to avoid test interference + let test_id = uuid::Uuid::new_v4().to_string()[..8].to_string(); + let test_username = format!("oidcuser_{}", test_id); + let test_email = format!("oidc_{}@example.com", test_id); + let test_subject = format!("oidc-subject-{}", test_id); + let create_user = CreateUser { - username: "oidcuser".to_string(), - email: "oidc@example.com".to_string(), + username: test_username.clone(), + email: test_email.clone(), password: "".to_string(), // Not used for OIDC role: Some(UserRole::User), }; let user = db.create_oidc_user( create_user, - "oidc-subject-123", + &test_subject, "https://provider.example.com", "oidc@example.com", ).await.unwrap(); - assert_eq!(user.username, "oidcuser"); - assert_eq!(user.email, "oidc@example.com"); - assert_eq!(user.oidc_subject, Some("oidc-subject-123".to_string())); + assert_eq!(user.username, test_username); + assert_eq!(user.email, test_email); + assert_eq!(user.oidc_subject, Some(test_subject)); assert_eq!(user.oidc_issuer, Some("https://provider.example.com".to_string())); - assert_eq!(user.oidc_email, Some("oidc@example.com".to_string())); + assert_eq!(user.oidc_email, Some(test_email.clone())); assert_eq!(user.auth_provider, AuthProvider::Oidc); assert!(user.password_hash.is_none()); } #[tokio::test] async fn test_get_user_by_oidc_subject() { - let (app, container) = create_test_app().await; + let (_app, container) = create_test_app().await; let port = container.get_host_port_ipv4(5432).await.unwrap(); let database_url = format!("postgresql://test:test@localhost:{}/test", port); let db = crate::db::Database::new(&database_url).await.unwrap(); + // Generate random identifiers to avoid test interference + let test_id = uuid::Uuid::new_v4().to_string()[..8].to_string(); + let test_username = format!("oidcuser_{}", test_id); + let test_email = format!("oidc_{}@example.com", test_id); + let test_subject = format!("oidc-subject-{}", test_id); + // Create OIDC user let create_user = CreateUser { - username: "oidcuser2".to_string(), - email: "oidc2@example.com".to_string(), + username: test_username, + email: test_email.clone(), password: "".to_string(), role: Some(UserRole::User), }; let created_user = db.create_oidc_user( create_user, - "oidc-subject-456", + &test_subject, "https://provider.example.com", - "oidc2@example.com", + &test_email, ).await.unwrap(); // Retrieve by OIDC subject let found_user = db.get_user_by_oidc_subject( - "oidc-subject-456", + &test_subject, "https://provider.example.com" ).await.unwrap(); assert!(found_user.is_some()); let user = found_user.unwrap(); assert_eq!(user.id, created_user.id); - assert_eq!(user.oidc_subject, Some("oidc-subject-456".to_string())); + assert_eq!(user.oidc_subject, Some(test_subject)); } #[tokio::test] async fn test_get_user_by_oidc_subject_not_found() { - let (app, container) = create_test_app().await; + let (_app, container) = create_test_app().await; let port = container.get_host_port_ipv4(5432).await.unwrap(); let database_url = format!("postgresql://test:test@localhost:{}/test", port); let db = crate::db::Database::new(&database_url).await.unwrap(); + // Generate random subject that definitely doesn't exist + let test_id = uuid::Uuid::new_v4().to_string(); + let nonexistent_subject = format!("nonexistent-subject-{}", test_id); + let found_user = db.get_user_by_oidc_subject( - "nonexistent-subject", + &nonexistent_subject, "https://provider.example.com" ).await.unwrap(); @@ -381,29 +398,35 @@ mod tests { #[tokio::test] async fn test_oidc_user_different_issuer() { - let (app, container) = create_test_app().await; + let (_app, container) = create_test_app().await; let port = container.get_host_port_ipv4(5432).await.unwrap(); let database_url = format!("postgresql://test:test@localhost:{}/test", port); let db = crate::db::Database::new(&database_url).await.unwrap(); + // Generate random identifiers to avoid test interference + let test_id = uuid::Uuid::new_v4().to_string()[..8].to_string(); + let test_username = format!("oidcuser_{}", test_id); + let test_email = format!("oidc_{}@example.com", test_id); + let test_subject = format!("same-subject-{}", test_id); + // Create OIDC user with one issuer let create_user = CreateUser { - username: "oidcuser3".to_string(), - email: "oidc3@example.com".to_string(), + username: test_username, + email: test_email.clone(), password: "".to_string(), role: Some(UserRole::User), }; db.create_oidc_user( create_user, - "same-subject", + &test_subject, "https://provider1.example.com", - "oidc3@example.com", + &test_email, ).await.unwrap(); // Try to find with different issuer (should not find) let found_user = db.get_user_by_oidc_subject( - "same-subject", + &test_subject, "https://provider2.example.com" ).await.unwrap();