mod common;
use arium::auth;
use serial_test::serial;
const ENV: &str = "DX_AUTH_BOOTSTRAP_ADMIN_EMAIL";
#[tokio::test]
#[serial]
async fn first_signup_with_no_env_var_becomes_admin() {
let _g = common::EnvGuard::unset(ENV);
let pool = common::pool().await;
let uid = common::make_user(&pool, "founder@example.com", "hunter22!").await;
let roles = auth::get_user_role_ids(&pool, uid).await.unwrap();
assert!(
roles.contains(&auth::role::ADMIN),
"first non-anonymous user must auto-promote: {roles:?}",
);
}
#[tokio::test]
#[serial]
async fn second_signup_is_member_only() {
let _g = common::EnvGuard::unset(ENV);
let pool = common::pool().await;
common::make_user(&pool, "founder@example.com", "hunter22!").await;
let second = common::make_user(&pool, "regular@example.com", "hunter22!").await;
let roles = auth::get_user_role_ids(&pool, second).await.unwrap();
assert!(!roles.contains(&auth::role::ADMIN), "{roles:?}");
assert!(roles.contains(&auth::role::MEMBER), "{roles:?}");
}
#[tokio::test]
#[serial]
async fn env_var_match_promotes_on_signup_even_when_not_first() {
let _g = common::EnvGuard::set(ENV, "admin@example.com");
let pool = common::pool().await;
common::make_user(&pool, "first@example.com", "hunter22!").await;
let target = common::make_user(&pool, "admin@example.com", "hunter22!").await;
let roles = auth::get_user_role_ids(&pool, target).await.unwrap();
assert!(roles.contains(&auth::role::ADMIN), "{roles:?}");
}
#[tokio::test]
#[serial]
async fn env_var_match_is_case_insensitive() {
let _g = common::EnvGuard::set(ENV, "Admin@Example.COM");
let pool = common::pool().await;
common::make_user(&pool, "first@example.com", "hunter22!").await;
let target = common::make_user(&pool, "admin@example.com", "hunter22!").await;
assert!(
auth::get_user_role_ids(&pool, target)
.await
.unwrap()
.contains(&auth::role::ADMIN),
);
}
#[tokio::test]
#[serial]
async fn sync_bootstrap_admin_promotes_a_preexisting_user_idempotently() {
let _g = common::EnvGuard::set(ENV, "ops@example.com");
let pool = common::pool().await;
let target = {
let _g_unset = common::EnvGuard::unset(ENV);
common::make_user(&pool, "ops@example.com", "hunter22!").await
};
auth::revoke_role(&pool, target, auth::role::ADMIN)
.await
.unwrap();
assert!(
!auth::get_user_role_ids(&pool, target)
.await
.unwrap()
.contains(&auth::role::ADMIN),
);
auth::sync_bootstrap_admin(&pool).await.unwrap();
assert!(
auth::get_user_role_ids(&pool, target)
.await
.unwrap()
.contains(&auth::role::ADMIN),
);
auth::sync_bootstrap_admin(&pool).await.unwrap();
let count: i64 =
sqlx::query_scalar("SELECT COUNT(*) FROM user_roles WHERE user_id = $1 AND role_id = $2")
.bind(target)
.bind(auth::role::ADMIN)
.fetch_one(&pool)
.await
.unwrap();
assert_eq!(count, 1);
}
#[tokio::test]
#[serial]
async fn sync_bootstrap_admin_is_noop_when_email_unknown() {
let _g = common::EnvGuard::set(ENV, "ghost@example.com");
let pool = common::pool().await;
auth::sync_bootstrap_admin(&pool).await.unwrap();
}
#[tokio::test]
#[serial]
async fn self_recovery_after_last_admin_is_soft_deleted() {
let _g = common::EnvGuard::unset(ENV);
let pool = common::pool().await;
let first = common::make_user(&pool, "first@example.com", "hunter22!").await;
assert!(
auth::get_user_role_ids(&pool, first)
.await
.unwrap()
.contains(&auth::role::ADMIN)
);
auth::soft_delete_user(&pool, first).await.unwrap();
let next = common::make_user(&pool, "next@example.com", "hunter22!").await;
assert!(
auth::get_user_role_ids(&pool, next)
.await
.unwrap()
.contains(&auth::role::ADMIN),
"self-recovery: with zero admins, next signup becomes admin",
);
}
#[tokio::test]
#[serial]
async fn alias_env_var_is_honored() {
let _g_primary = common::EnvGuard::unset(ENV);
let _g_alias = common::EnvGuard::set("BOOTSTRAP_ADMIN_EMAIL", "ops@example.com");
let pool = common::pool().await;
common::make_user(&pool, "first@example.com", "hunter22!").await;
let target = common::make_user(&pool, "ops@example.com", "hunter22!").await;
assert!(
auth::get_user_role_ids(&pool, target)
.await
.unwrap()
.contains(&auth::role::ADMIN),
);
}