feat(tests): resolve admin integration test issues

This commit is contained in:
perf3ct 2025-06-22 17:28:45 +00:00
parent ddecb5bab7
commit 1555b8bd4d
5 changed files with 108 additions and 37 deletions

1
.gitignore vendored
View File

@ -8,3 +8,4 @@ frontend/dist/
readur_uploads/
readur_watch/
test-results/
uploads/

View File

@ -9,7 +9,15 @@ use std::sync::Arc;
use serde::Serialize;
use utoipa::ToSchema;
use crate::{auth::AuthUser, AppState};
use crate::{auth::AuthUser, AppState, models::UserRole};
fn require_admin(auth_user: &AuthUser) -> Result<(), StatusCode> {
if auth_user.user.role != UserRole::Admin {
Err(StatusCode::FORBIDDEN)
} else {
Ok(())
}
}
#[derive(Serialize, ToSchema)]
pub struct SystemMetrics {
@ -83,8 +91,9 @@ pub fn router() -> Router<Arc<AppState>> {
)]
pub async fn get_system_metrics(
State(state): State<Arc<AppState>>,
_auth_user: AuthUser, // Require authentication
auth_user: AuthUser,
) -> Result<Json<SystemMetrics>, StatusCode> {
require_admin(&auth_user)?;
let timestamp = chrono::Utc::now().timestamp();
// Collect all metrics concurrently for better performance

View File

@ -41,8 +41,9 @@ pub fn router() -> Router<Arc<AppState>> {
)]
async fn get_queue_stats(
State(state): State<Arc<AppState>>,
_auth_user: AuthUser, // Require authentication
auth_user: AuthUser,
) -> Result<Json<serde_json::Value>, StatusCode> {
require_admin(&auth_user)?;
let queue_service = OcrQueueService::new(state.db.clone(), state.db.get_pool().clone(), 1);
let stats = queue_service
@ -74,8 +75,9 @@ async fn get_queue_stats(
)]
async fn requeue_failed(
State(state): State<Arc<AppState>>,
_auth_user: AuthUser, // Require authentication
auth_user: AuthUser,
) -> Result<Json<serde_json::Value>, StatusCode> {
require_admin(&auth_user)?;
let queue_service = OcrQueueService::new(state.db.clone(), state.db.get_pool().clone(), 1);
let count = queue_service

View File

@ -10,10 +10,18 @@ use uuid::Uuid;
use crate::{
auth::AuthUser,
models::{CreateUser, UpdateUser, UserResponse},
models::{CreateUser, UpdateUser, UserResponse, UserRole},
AppState,
};
fn require_admin(auth_user: &AuthUser) -> Result<(), StatusCode> {
if auth_user.user.role != UserRole::Admin {
Err(StatusCode::FORBIDDEN)
} else {
Ok(())
}
}
pub fn router() -> Router<Arc<AppState>> {
Router::new()
.route("/", get(list_users).post(create_user))
@ -33,9 +41,10 @@ pub fn router() -> Router<Arc<AppState>> {
)
)]
async fn list_users(
_auth_user: AuthUser,
auth_user: AuthUser,
State(state): State<Arc<AppState>>,
) -> Result<Json<Vec<UserResponse>>, StatusCode> {
require_admin(&auth_user)?;
let users = state
.db
.get_all_users()
@ -63,10 +72,11 @@ async fn list_users(
)
)]
async fn get_user(
_auth_user: AuthUser,
auth_user: AuthUser,
State(state): State<Arc<AppState>>,
Path(id): Path<Uuid>,
) -> Result<Json<UserResponse>, StatusCode> {
require_admin(&auth_user)?;
let user = state
.db
.get_user_by_id(id)
@ -92,10 +102,11 @@ async fn get_user(
)
)]
async fn create_user(
_auth_user: AuthUser,
auth_user: AuthUser,
State(state): State<Arc<AppState>>,
Json(user_data): Json<CreateUser>,
) -> Result<Json<UserResponse>, StatusCode> {
require_admin(&auth_user)?;
let user = state
.db
.create_user(user_data)
@ -123,11 +134,12 @@ async fn create_user(
)
)]
async fn update_user(
_auth_user: AuthUser,
auth_user: AuthUser,
State(state): State<Arc<AppState>>,
Path(id): Path<Uuid>,
Json(update_data): Json<UpdateUser>,
) -> Result<Json<UserResponse>, StatusCode> {
require_admin(&auth_user)?;
let user = state
.db
.update_user(id, update_data.username, update_data.email, update_data.password)
@ -158,6 +170,8 @@ async fn delete_user(
State(state): State<Arc<AppState>>,
Path(id): Path<Uuid>,
) -> Result<StatusCode, StatusCode> {
require_admin(&auth_user)?;
// Prevent users from deleting themselves
if auth_user.user.id == id {
return Err(StatusCode::FORBIDDEN);

View File

@ -48,28 +48,30 @@ impl RBACTestClient {
/// Setup all test users (admin, user1, user2)
async fn setup_all_users(&mut self) -> Result<(), Box<dyn std::error::Error>> {
let timestamp = std::time::SystemTime::now()
// Use UUID for guaranteed uniqueness across concurrent test execution
let test_id = Uuid::new_v4().simple().to_string();
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis();
.as_nanos();
// Setup admin user
let admin_username = format!("rbac_admin_{}", timestamp);
let admin_email = format!("rbac_admin_{}@example.com", timestamp);
let admin_username = format!("rbac_admin_{}_{}", test_id, nanos);
let admin_email = format!("rbac_admin_{}@{}.example.com", test_id, nanos);
let (admin_token, admin_id) = self.register_and_login_user(&admin_username, &admin_email, UserRole::Admin).await?;
self.admin_token = Some(admin_token);
self.admin_user_id = admin_id;
// Setup first regular user
let user1_username = format!("rbac_user1_{}", timestamp);
let user1_email = format!("rbac_user1_{}@example.com", timestamp);
let user1_username = format!("rbac_user1_{}_{}", test_id, nanos);
let user1_email = format!("rbac_user1_{}@{}.example.com", test_id, nanos);
let (user1_token, user1_id) = self.register_and_login_user(&user1_username, &user1_email, UserRole::User).await?;
self.user1_token = Some(user1_token);
self.user1_user_id = user1_id;
// Setup second regular user
let user2_username = format!("rbac_user2_{}", timestamp);
let user2_email = format!("rbac_user2_{}@example.com", timestamp);
let user2_username = format!("rbac_user2_{}_{}", test_id, nanos);
let user2_email = format!("rbac_user2_{}@{}.example.com", test_id, nanos);
let (user2_token, user2_id) = self.register_and_login_user(&user2_username, &user2_email, UserRole::User).await?;
self.user2_token = Some(user2_token);
self.user2_user_id = user2_id;
@ -79,26 +81,57 @@ impl RBACTestClient {
/// Helper to register and login a single user
async fn register_and_login_user(&self, username: &str, email: &str, role: UserRole) -> Result<(String, Option<String>), Box<dyn std::error::Error>> {
let password = "rbacpassword123";
// Register user
let user_data = CreateUser {
username: username.to_string(),
email: email.to_string(),
password: password.to_string(),
role: Some(role),
};
let register_response = self.client
.post(&format!("{}/api/auth/register", get_base_url()))
.json(&user_data)
.send()
.await?;
if !register_response.status().is_success() {
return Err(format!("Registration failed for {}: {}", username, register_response.text().await?).into());
// Try up to 3 times with different unique identifiers if we get conflicts
for attempt in 0..3 {
let (actual_username, actual_email) = if attempt == 0 {
(username.to_string(), email.to_string())
} else {
let retry_id = Uuid::new_v4().simple().to_string();
(format!("{}_{}", username, retry_id), format!("retry_{}_{}", retry_id, email))
};
let password = "rbacpassword123";
// Register user
let user_data = CreateUser {
username: actual_username.clone(),
email: actual_email.clone(),
password: password.to_string(),
role: Some(role),
};
let register_response = self.client
.post(&format!("{}/api/auth/register", get_base_url()))
.json(&user_data)
.send()
.await?;
if register_response.status().is_success() {
// Registration successful, now login
return self.login_user(&actual_username, password).await;
}
let error_text = register_response.text().await?;
// If it's not a duplicate key error, fail immediately
if !error_text.contains("duplicate key") && !error_text.contains("already exists") {
return Err(format!("Registration failed for {}: {}", actual_username, error_text).into());
}
// If it's a duplicate key error and this was our last attempt, fail
if attempt == 2 {
return Err(format!("Registration failed after 3 attempts for {}: {}", username, error_text).into());
}
// Otherwise, try again with a different unique identifier
}
Err("Unexpected error in register_and_login_user".into())
}
/// Helper to login a user and return token and user ID
async fn login_user(&self, username: &str, password: &str) -> Result<(String, Option<String>), Box<dyn std::error::Error>> {
// Login to get token
let login_data = LoginRequest {
username: username.to_string(),
@ -142,7 +175,16 @@ impl RBACTestClient {
UserType::User2 => self.user2_token.as_ref(),
}.ok_or("User not set up")?;
let part = reqwest::multipart::Part::text(content.to_string())
// Generate unique content to prevent file hash collisions
let unique_id = Uuid::new_v4();
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos();
let unique_content = format!("{}\n\nUnique ID: {}\nTimestamp: {}\nRandom: {}",
content, unique_id, nanos, Uuid::new_v4());
let part = reqwest::multipart::Part::text(unique_content)
.file_name(filename.to_string())
.mime_str("text/plain")?;
let form = reqwest::multipart::Form::new()
@ -181,7 +223,10 @@ impl RBACTestClient {
return Err(format!("Get documents failed: {}", response.text().await?).into());
}
let documents: Vec<Value> = response.json().await?;
let response_json: Value = response.json().await?;
let documents: Vec<Value> = serde_json::from_value(
response_json["documents"].clone()
)?;
Ok(documents)
}