Add tests
This commit is contained in:
@@ -17,8 +17,6 @@ use auth::{auth_middleware, hash_password};
|
||||
use models::ApiResponse;
|
||||
use routes::{health, projects, tasks, users};
|
||||
|
||||
|
||||
|
||||
async fn echo_handler(
|
||||
Json(payload): Json<serde_json::Value>,
|
||||
) -> ResponseJson<ApiResponse<serde_json::Value>> {
|
||||
@@ -32,10 +30,15 @@ async fn echo_handler(
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
// Load environment variables from .env file
|
||||
dotenvy::dotenv().ok();
|
||||
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR")?;
|
||||
dotenvy::from_path(format!("{manifest_dir}/.env")).ok();
|
||||
// dotenvy::dotenv().ok();
|
||||
|
||||
tracing_subscriber::fmt()
|
||||
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env().add_directive("bloop_backend=debug".parse()?))
|
||||
.with_env_filter(
|
||||
tracing_subscriber::EnvFilter::from_default_env()
|
||||
.add_directive("bloop_backend=debug".parse()?),
|
||||
)
|
||||
.init();
|
||||
|
||||
// Database connection
|
||||
@@ -87,8 +90,7 @@ async fn create_admin_account(pool: &sqlx::PgPool) -> anyhow::Result<()> {
|
||||
use uuid::Uuid;
|
||||
|
||||
let admin_email = "admin@example.com";
|
||||
let admin_password = env::var("ADMIN_PASSWORD")
|
||||
.unwrap_or_else(|_| "admin123".to_string());
|
||||
let admin_password = env::var("ADMIN_PASSWORD").unwrap_or_else(|_| "admin123".to_string());
|
||||
|
||||
// Check if admin already exists
|
||||
let existing_admin = sqlx::query!(
|
||||
@@ -112,7 +114,7 @@ async fn create_admin_account(pool: &sqlx::PgPool) -> anyhow::Result<()> {
|
||||
)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
|
||||
tracing::info!("Updated admin account");
|
||||
} else {
|
||||
// Create new admin account
|
||||
|
||||
@@ -319,3 +319,357 @@ pub fn protected_users_router() -> Router {
|
||||
.route("/users", get(get_users).post(create_user))
|
||||
.route("/users/:id", get(get_user).put(update_user).delete(delete_user))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use axum::extract::Extension;
|
||||
use sqlx::PgPool;
|
||||
use uuid::Uuid;
|
||||
use chrono::Utc;
|
||||
use crate::models::user::{LoginRequest, CreateUser, UpdateUser};
|
||||
use crate::auth::{AuthUser, hash_password};
|
||||
|
||||
async fn create_test_user(pool: &PgPool, email: &str, password: &str, is_admin: bool) -> User {
|
||||
let id = Uuid::new_v4();
|
||||
let now = Utc::now();
|
||||
let password_hash = hash_password(password).unwrap();
|
||||
|
||||
sqlx::query_as!(
|
||||
User,
|
||||
"INSERT INTO users (id, email, password_hash, is_admin, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id, email, password_hash, is_admin, created_at, updated_at",
|
||||
id,
|
||||
email,
|
||||
password_hash,
|
||||
is_admin,
|
||||
now,
|
||||
now
|
||||
)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[sqlx::test]
|
||||
async fn test_login_success(pool: PgPool) {
|
||||
let user = create_test_user(&pool, "test@example.com", "password123", false).await;
|
||||
|
||||
let login_request = LoginRequest {
|
||||
email: "test@example.com".to_string(),
|
||||
password: "password123".to_string(),
|
||||
};
|
||||
|
||||
let result = login(Extension(pool), Json(login_request)).await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
let response = result.unwrap().0;
|
||||
assert!(response.success);
|
||||
assert!(response.data.is_some());
|
||||
assert_eq!(response.data.as_ref().unwrap().user.email, user.email);
|
||||
}
|
||||
|
||||
#[sqlx::test]
|
||||
async fn test_login_invalid_password(pool: PgPool) {
|
||||
create_test_user(&pool, "test@example.com", "password123", false).await;
|
||||
|
||||
let login_request = LoginRequest {
|
||||
email: "test@example.com".to_string(),
|
||||
password: "wrongpassword".to_string(),
|
||||
};
|
||||
|
||||
let result = login(Extension(pool), Json(login_request)).await;
|
||||
assert!(result.is_err());
|
||||
assert_eq!(result.unwrap_err(), StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
#[sqlx::test]
|
||||
async fn test_login_user_not_found(pool: PgPool) {
|
||||
let login_request = LoginRequest {
|
||||
email: "nonexistent@example.com".to_string(),
|
||||
password: "password123".to_string(),
|
||||
};
|
||||
|
||||
let result = login(Extension(pool), Json(login_request)).await;
|
||||
assert!(result.is_err());
|
||||
assert_eq!(result.unwrap_err(), StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
#[sqlx::test]
|
||||
async fn test_get_users_as_admin(pool: PgPool) {
|
||||
let admin_user = create_test_user(&pool, "admin@example.com", "password123", true).await;
|
||||
create_test_user(&pool, "user1@example.com", "password123", false).await;
|
||||
create_test_user(&pool, "user2@example.com", "password123", false).await;
|
||||
|
||||
let auth = AuthUser {
|
||||
user_id: admin_user.id,
|
||||
email: admin_user.email,
|
||||
is_admin: true,
|
||||
};
|
||||
|
||||
let result = get_users(auth, Extension(pool)).await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
let response = result.unwrap().0;
|
||||
assert!(response.success);
|
||||
assert!(response.data.is_some());
|
||||
assert_eq!(response.data.unwrap().len(), 3);
|
||||
}
|
||||
|
||||
#[sqlx::test]
|
||||
async fn test_get_user_own_profile(pool: PgPool) {
|
||||
let user = create_test_user(&pool, "test@example.com", "password123", false).await;
|
||||
|
||||
let auth = AuthUser {
|
||||
user_id: user.id,
|
||||
email: user.email.clone(),
|
||||
is_admin: false,
|
||||
};
|
||||
|
||||
let result = get_user(auth, Path(user.id), Extension(pool)).await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
let response = result.unwrap().0;
|
||||
assert!(response.success);
|
||||
assert!(response.data.is_some());
|
||||
assert_eq!(response.data.unwrap().email, user.email);
|
||||
}
|
||||
|
||||
#[sqlx::test]
|
||||
async fn test_get_user_forbidden_non_admin(pool: PgPool) {
|
||||
let user1 = create_test_user(&pool, "user1@example.com", "password123", false).await;
|
||||
let user2 = create_test_user(&pool, "user2@example.com", "password123", false).await;
|
||||
|
||||
let auth = AuthUser {
|
||||
user_id: user1.id,
|
||||
email: user1.email,
|
||||
is_admin: false,
|
||||
};
|
||||
|
||||
let result = get_user(auth, Path(user2.id), Extension(pool)).await;
|
||||
assert!(result.is_err());
|
||||
assert_eq!(result.unwrap_err(), StatusCode::FORBIDDEN);
|
||||
}
|
||||
|
||||
#[sqlx::test]
|
||||
async fn test_get_user_admin_can_view_any(pool: PgPool) {
|
||||
let admin_user = create_test_user(&pool, "admin@example.com", "password123", true).await;
|
||||
let regular_user = create_test_user(&pool, "user@example.com", "password123", false).await;
|
||||
|
||||
let auth = AuthUser {
|
||||
user_id: admin_user.id,
|
||||
email: admin_user.email,
|
||||
is_admin: true,
|
||||
};
|
||||
|
||||
let result = get_user(auth, Path(regular_user.id), Extension(pool)).await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
let response = result.unwrap().0;
|
||||
assert!(response.success);
|
||||
assert!(response.data.is_some());
|
||||
assert_eq!(response.data.unwrap().email, regular_user.email);
|
||||
}
|
||||
|
||||
#[sqlx::test]
|
||||
async fn test_create_user_as_admin(pool: PgPool) {
|
||||
let admin_user = create_test_user(&pool, "admin@example.com", "password123", true).await;
|
||||
|
||||
let auth = AuthUser {
|
||||
user_id: admin_user.id,
|
||||
email: admin_user.email,
|
||||
is_admin: true,
|
||||
};
|
||||
|
||||
let create_request = CreateUser {
|
||||
email: "newuser@example.com".to_string(),
|
||||
password: "password123".to_string(),
|
||||
is_admin: Some(false),
|
||||
};
|
||||
|
||||
let result = create_user(auth, Extension(pool), Json(create_request)).await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
let response = result.unwrap().0;
|
||||
assert!(response.success);
|
||||
assert!(response.data.is_some());
|
||||
assert_eq!(response.data.unwrap().email, "newuser@example.com");
|
||||
}
|
||||
|
||||
#[sqlx::test]
|
||||
async fn test_create_user_forbidden_non_admin(pool: PgPool) {
|
||||
let regular_user = create_test_user(&pool, "user@example.com", "password123", false).await;
|
||||
|
||||
let auth = AuthUser {
|
||||
user_id: regular_user.id,
|
||||
email: regular_user.email,
|
||||
is_admin: false,
|
||||
};
|
||||
|
||||
let create_request = CreateUser {
|
||||
email: "newuser@example.com".to_string(),
|
||||
password: "password123".to_string(),
|
||||
is_admin: Some(false),
|
||||
};
|
||||
|
||||
let result = create_user(auth, Extension(pool), Json(create_request)).await;
|
||||
assert!(result.is_err());
|
||||
assert_eq!(result.unwrap_err(), StatusCode::FORBIDDEN);
|
||||
}
|
||||
|
||||
#[sqlx::test]
|
||||
async fn test_create_user_duplicate_email(pool: PgPool) {
|
||||
let admin_user = create_test_user(&pool, "admin@example.com", "password123", true).await;
|
||||
create_test_user(&pool, "existing@example.com", "password123", false).await;
|
||||
|
||||
let auth = AuthUser {
|
||||
user_id: admin_user.id,
|
||||
email: admin_user.email,
|
||||
is_admin: true,
|
||||
};
|
||||
|
||||
let create_request = CreateUser {
|
||||
email: "existing@example.com".to_string(),
|
||||
password: "password123".to_string(),
|
||||
is_admin: Some(false),
|
||||
};
|
||||
|
||||
let result = create_user(auth, Extension(pool), Json(create_request)).await;
|
||||
assert!(result.is_err());
|
||||
assert_eq!(result.unwrap_err(), StatusCode::CONFLICT);
|
||||
}
|
||||
|
||||
#[sqlx::test]
|
||||
async fn test_update_user_own_profile(pool: PgPool) {
|
||||
let user = create_test_user(&pool, "user@example.com", "password123", false).await;
|
||||
|
||||
let auth = AuthUser {
|
||||
user_id: user.id,
|
||||
email: user.email.clone(),
|
||||
is_admin: false,
|
||||
};
|
||||
|
||||
let update_request = UpdateUser {
|
||||
email: Some("newemail@example.com".to_string()),
|
||||
password: Some("newpassword123".to_string()),
|
||||
is_admin: None,
|
||||
};
|
||||
|
||||
let result = update_user(auth, Path(user.id), Extension(pool), Json(update_request)).await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
let response = result.unwrap().0;
|
||||
assert!(response.success);
|
||||
assert!(response.data.is_some());
|
||||
assert_eq!(response.data.unwrap().email, "newemail@example.com");
|
||||
}
|
||||
|
||||
#[sqlx::test]
|
||||
async fn test_update_user_forbidden_non_admin(pool: PgPool) {
|
||||
let user1 = create_test_user(&pool, "user1@example.com", "password123", false).await;
|
||||
let user2 = create_test_user(&pool, "user2@example.com", "password123", false).await;
|
||||
|
||||
let auth = AuthUser {
|
||||
user_id: user1.id,
|
||||
email: user1.email,
|
||||
is_admin: false,
|
||||
};
|
||||
|
||||
let update_request = UpdateUser {
|
||||
email: Some("newemail@example.com".to_string()),
|
||||
password: None,
|
||||
is_admin: None,
|
||||
};
|
||||
|
||||
let result = update_user(auth, Path(user2.id), Extension(pool), Json(update_request)).await;
|
||||
assert!(result.is_err());
|
||||
assert_eq!(result.unwrap_err(), StatusCode::FORBIDDEN);
|
||||
}
|
||||
|
||||
#[sqlx::test]
|
||||
async fn test_delete_user_as_admin(pool: PgPool) {
|
||||
let admin_user = create_test_user(&pool, "admin@example.com", "password123", true).await;
|
||||
let user_to_delete = create_test_user(&pool, "delete@example.com", "password123", false).await;
|
||||
|
||||
let auth = AuthUser {
|
||||
user_id: admin_user.id,
|
||||
email: admin_user.email,
|
||||
is_admin: true,
|
||||
};
|
||||
|
||||
let result = delete_user(auth, Path(user_to_delete.id), Extension(pool)).await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
let response = result.unwrap().0;
|
||||
assert!(response.success);
|
||||
assert_eq!(response.message.unwrap(), "User deleted successfully");
|
||||
}
|
||||
|
||||
#[sqlx::test]
|
||||
async fn test_delete_user_forbidden_non_admin(pool: PgPool) {
|
||||
let user1 = create_test_user(&pool, "user1@example.com", "password123", false).await;
|
||||
let user2 = create_test_user(&pool, "user2@example.com", "password123", false).await;
|
||||
|
||||
let auth = AuthUser {
|
||||
user_id: user1.id,
|
||||
email: user1.email,
|
||||
is_admin: false,
|
||||
};
|
||||
|
||||
let result = delete_user(auth, Path(user2.id), Extension(pool)).await;
|
||||
assert!(result.is_err());
|
||||
assert_eq!(result.unwrap_err(), StatusCode::FORBIDDEN);
|
||||
}
|
||||
|
||||
#[sqlx::test]
|
||||
async fn test_delete_user_self_forbidden(pool: PgPool) {
|
||||
let admin_user = create_test_user(&pool, "admin@example.com", "password123", true).await;
|
||||
|
||||
let auth = AuthUser {
|
||||
user_id: admin_user.id,
|
||||
email: admin_user.email.clone(),
|
||||
is_admin: true,
|
||||
};
|
||||
|
||||
let result = delete_user(auth, Path(admin_user.id), Extension(pool)).await;
|
||||
assert!(result.is_err());
|
||||
assert_eq!(result.unwrap_err(), StatusCode::FORBIDDEN);
|
||||
}
|
||||
|
||||
#[sqlx::test]
|
||||
async fn test_get_current_user(pool: PgPool) {
|
||||
let user = create_test_user(&pool, "user@example.com", "password123", false).await;
|
||||
|
||||
let auth = AuthUser {
|
||||
user_id: user.id,
|
||||
email: user.email.clone(),
|
||||
is_admin: false,
|
||||
};
|
||||
|
||||
let result = get_current_user(auth, Extension(pool)).await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
let response = result.unwrap().0;
|
||||
assert!(response.success);
|
||||
assert!(response.data.is_some());
|
||||
assert_eq!(response.data.unwrap().email, user.email);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_check_auth_status() {
|
||||
let auth = AuthUser {
|
||||
user_id: Uuid::new_v4(),
|
||||
email: "test@example.com".to_string(),
|
||||
is_admin: true,
|
||||
};
|
||||
|
||||
let response = check_auth_status(auth.clone()).await.0;
|
||||
assert!(response.success);
|
||||
assert!(response.data.is_some());
|
||||
|
||||
let data = response.data.unwrap();
|
||||
assert_eq!(data["authenticated"], true);
|
||||
assert_eq!(data["user_id"], auth.user_id.to_string());
|
||||
assert_eq!(data["email"], auth.email);
|
||||
assert_eq!(data["is_admin"], auth.is_admin);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"backend:dev": "cargo watch -x 'run --manifest-path backend/Cargo.toml'",
|
||||
"backend:build": "cargo build --release --manifest-path backend/Cargo.toml",
|
||||
"backend:run": "cargo run --manifest-path backend/Cargo.toml",
|
||||
"backend:test": "cargo test --lib",
|
||||
"generate-types": "cd backend && cargo run --bin generate_types"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
Reference in New Issue
Block a user