use std::sync::Once;
use crate::passkey::CredentialId;
use crate::session::UserId;
pub async fn init_test_environment() {
static ENV_INIT: Once = Once::new();
ENV_INIT.call_once(|| {
println!("🧪 Loading test environment (.env_test)");
if dotenvy::from_filename_override(".env_test").is_err() {
dotenvy::dotenv().ok();
}
if let Some(db_path) = extract_sqlite_file_path()
&& std::fs::remove_file(&db_path).is_err()
{
}
});
ensure_database_initialized().await;
}
fn extract_sqlite_file_path() -> Option<String> {
if let Ok(url) = std::env::var("GENERIC_DATA_STORE_URL") {
extract_sqlite_file_path_from_url(&url)
} else {
None
}
}
async fn ensure_database_initialized() {
use crate::oauth2::OAuth2Store;
use crate::passkey::PasskeyStore;
use crate::userdb::UserStore;
if let Err(e) = UserStore::init().await {
eprintln!("Warning: Failed to initialize UserStore: {e}");
}
if let Err(e) = OAuth2Store::init().await {
eprintln!("Warning: Failed to initialize OAuth2Store: {e}");
}
if let Err(e) = PasskeyStore::init().await {
eprintln!("Warning: Failed to initialize PasskeyStore: {e}");
}
create_first_user_if_needed().await;
}
async fn create_first_user_if_needed() {
use crate::userdb::{User, UserStore};
use std::sync::LazyLock;
use tokio::sync::Mutex;
static FIRST_USER_CREATION_MUTEX: LazyLock<Mutex<()>> = LazyLock::new(|| Mutex::new(()));
let _guard = FIRST_USER_CREATION_MUTEX.lock().await;
match UserStore::get_all_users().await {
Ok(users) if users.is_empty() => {
let mut first_user = User::new(
"first-user".to_string(),
"first-user@example.com".to_string(),
"First User".to_string(),
);
first_user.is_admin = true;
match UserStore::upsert_user(first_user).await {
Ok(created_user) => {
println!("✅ Created first test user with sequence_number = 1");
create_first_user_oauth2_account(&created_user.id).await;
create_first_user_passkey_credential(&created_user.id).await;
}
Err(e) => {
eprintln!("Warning: Failed to create first test user: {e}");
}
}
}
Ok(users) => {
if let Some(first) = users.iter().find(|u| u.sequence_number == Some(1)) {
println!("✅ First user already exists: {}", first.id);
if !first.has_admin_privileges() {
println!("🔧 Updating first user to have admin privileges...");
let mut updated_user = first.clone();
updated_user.is_admin = true;
if let Err(e) = UserStore::upsert_user(updated_user).await {
eprintln!("Warning: Failed to update first user admin status: {e}");
} else {
println!("✅ First user now has admin privileges");
}
}
ensure_first_user_has_oauth2_account(&first.id).await;
ensure_first_user_has_passkey_credential(&first.id).await;
}
}
Err(e) => {
eprintln!("Warning: Failed to check existing users: {e}");
}
}
}
async fn create_first_user_oauth2_account(user_id: &str) {
use crate::oauth2::{OAuth2Account, OAuth2Store};
use chrono::Utc;
let now = Utc::now();
let provider = "google";
let provider_user_id = format!("{provider}_first-user-test-google-id");
let test_oauth2_account = OAuth2Account {
sequence_number: None,
id: "first-user-oauth2-account".to_string(),
user_id: user_id.to_string(),
provider: provider.to_string(),
provider_user_id: provider_user_id.to_string(),
name: "First User".to_string(),
email: "first-user@example.com".to_string(),
picture: Some("https://example.com/avatar/first-user.jpg".to_string()),
metadata: serde_json::json!({"test_account": true, "created_by": "test_utils"}),
created_at: now,
updated_at: now,
};
match OAuth2Store::upsert_oauth2_account(test_oauth2_account).await {
Ok(_account) => {
println!("✅ Created first user OAuth2 account for testing");
}
Err(e) => {
eprintln!("Warning: Failed to create first user OAuth2 account: {e}");
}
}
}
async fn ensure_first_user_has_oauth2_account(user_id: &str) {
use crate::oauth2::OAuth2Store;
let user_id_typed = match UserId::new(user_id.to_string()) {
Ok(id) => id,
Err(e) => {
eprintln!("Warning: Failed to create UserId for '{user_id}': {e}");
return;
}
};
match OAuth2Store::get_oauth2_accounts(user_id_typed).await {
Ok(accounts) if accounts.is_empty() => {
println!("ℹ️ First user exists but has no OAuth2 account, creating one...");
create_first_user_oauth2_account(user_id).await;
}
Ok(accounts) => {
println!("✅ First user has {} OAuth2 account(s)", accounts.len());
}
Err(e) => {
eprintln!("Warning: Failed to check first user OAuth2 accounts: {e}");
}
}
}
async fn create_first_user_passkey_credential(user_id: &str) {
use crate::passkey::{PasskeyCredential, PasskeyStore};
use chrono::Utc;
let now = Utc::now();
let public_key = generate_first_user_public_key();
let test_passkey_credential = PasskeyCredential {
sequence_number: None,
credential_id: "first-user-test-passkey-credential".to_string(),
user_id: user_id.to_string(),
public_key,
aaguid: "00000000-0000-0000-0000-000000000000".to_string(), rp_id: "localhost".to_string(),
counter: 0,
user: serde_json::from_value(serde_json::json!({
"user_handle": "first-user-handle",
"name": "first-user@example.com",
"displayName": "First User"
}))
.expect("Valid user entity JSON"),
created_at: now,
updated_at: now,
last_used_at: now,
};
match PasskeyStore::store_credential(
CredentialId::new("first-user-test-passkey-credential".to_string())
.expect("Valid credential ID"),
test_passkey_credential,
)
.await
{
Ok(_) => {
println!("✅ Created first user Passkey credential for testing");
}
Err(e) => {
eprintln!("Warning: Failed to create first user Passkey credential: {e}");
}
}
}
fn generate_first_user_public_key() -> String {
"BBtOg4PEjnY2yQkrPjL832Obw0qJxiR-vIoUjjMmkKbyNjO4tT3blJAlPI5Y39nDiNkn7UnkCFZIS39cYp9nLPs"
.to_string()
}
async fn ensure_first_user_has_passkey_credential(user_id: &str) {
use crate::passkey::{CredentialSearchField, PasskeyStore};
let user_id_typed = match crate::session::UserId::new(user_id.to_string()) {
Ok(id) => id,
Err(e) => {
eprintln!("Warning: Failed to create UserId for '{user_id}': {e}");
return;
}
};
match PasskeyStore::get_credentials_by(CredentialSearchField::UserId(user_id_typed)).await {
Ok(credentials) if credentials.is_empty() => {
println!("ℹ️ First user exists but has no Passkey credential, creating one...");
create_first_user_passkey_credential(user_id).await;
}
Ok(credentials) => {
println!(
"✅ First user has {} Passkey credential(s)",
credentials.len()
);
}
Err(e) => {
eprintln!("Warning: Failed to check first user Passkey credentials: {e}");
}
}
}
pub(crate) async fn restore_first_user_after_deletion() {
use crate::storage::GENERIC_DATA_STORE;
use crate::userdb::DB_TABLE_USERS;
{
let store = GENERIC_DATA_STORE.lock().await;
let pool = store.as_sqlite().expect("Test database must be SQLite");
let table_name = DB_TABLE_USERS.as_str();
let now = chrono::Utc::now();
sqlx::query(&format!(
r#"
INSERT OR REPLACE INTO {table_name}
(sequence_number, id, account, label, is_admin, created_at, updated_at)
VALUES (1, 'first-user', 'first-user@example.com', 'First User', true, ?, ?)
"#
))
.bind(now)
.bind(now)
.execute(pool)
.await
.expect("Failed to restore first user with sequence_number=1");
}
create_first_user_oauth2_account("first-user").await;
create_first_user_passkey_credential("first-user").await;
}
fn extract_sqlite_file_path_from_url(url: &str) -> Option<String> {
if url.starts_with("sqlite:") {
let path = url.strip_prefix("sqlite:")?;
if path.starts_with("file:") {
let file_path = path.strip_prefix("file:")?;
let path_only = file_path.split('?').next()?;
if path_only.contains(":memory:") {
return None;
}
Some(path_only.to_string())
} else {
let path = path.strip_prefix("//").unwrap_or(path);
if path.contains(":memory:") {
return None;
}
Some(path.to_string())
}
} else {
None
}
}
pub fn get_test_origin() -> String {
std::env::var("ORIGIN").unwrap_or_else(|_| "http://127.0.0.1:3000".to_string())
}
pub fn run_child_with_env(
test_name: &str,
env_name: &str,
env_value: &str,
) -> std::process::Output {
std::process::Command::new(std::env::current_exe().unwrap())
.args([test_name, "--exact", "--nocapture"])
.env("__TEST_ENV_VAR_CHILD", "1")
.env(env_name, env_value)
.output()
.expect("Failed to spawn child process")
}
pub fn run_child_without_env(test_name: &str, env_name: &str) -> std::process::Output {
std::process::Command::new(std::env::current_exe().unwrap())
.args([test_name, "--exact", "--nocapture"])
.env("__TEST_ENV_VAR_CHILD", "1")
.env_remove(env_name)
.output()
.expect("Failed to spawn child process")
}
#[cfg(test)]
mod tests;