Add tests

This commit is contained in:
Louis Knight-Webb
2025-06-15 16:53:26 -04:00
parent a435e3ce5d
commit 839f5faba2
3 changed files with 364 additions and 7 deletions

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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": {