use assert_cmd::Command;
use predicates::prelude::*;
use serial_test::serial;
use std::fs;
use std::path::PathBuf;
use std::sync::Mutex;
static CONFIG_LOCK: Mutex<()> = Mutex::new(());
fn get_real_config_dir() -> PathBuf {
directories::ProjectDirs::from("com", "oauth-db", "oauth-db-cli")
.expect("Failed to get config dir")
.config_dir()
.to_path_buf()
}
fn backup_config() -> Option<String> {
let config_path = get_real_config_dir().join("config.toml");
if config_path.exists() {
fs::read_to_string(&config_path).ok()
} else {
None
}
}
fn restore_config(backup: Option<String>) {
let config_path = get_real_config_dir().join("config.toml");
if let Some(content) = backup {
fs::write(&config_path, content).ok();
} else {
fs::remove_file(&config_path).ok();
}
}
fn setup_admin_account() {
let config_dir = get_real_config_dir();
fs::create_dir_all(&config_dir).unwrap();
let config = r#"
[server]
url = "http://localhost:28080"
[output]
format = "table"
color = true
[log]
level = "info"
[[accounts]]
name = "admin@localhost"
server = "http://localhost:28080"
username = "admin"
token = "admin_valid_token"
default = true
"#;
fs::write(config_dir.join("config.toml"), config).unwrap();
}
fn setup_regular_user_account() {
let config_dir = get_real_config_dir();
fs::create_dir_all(&config_dir).unwrap();
let config = r#"
[server]
url = "http://localhost:28080"
[output]
format = "table"
color = true
[log]
level = "info"
[[accounts]]
name = "user@localhost"
server = "http://localhost:28080"
username = "user"
token = "user_valid_token"
default = true
"#;
fs::write(config_dir.join("config.toml"), config).unwrap();
}
fn generate_username() -> String {
use std::time::{SystemTime, UNIX_EPOCH};
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
format!("test_user_{}", timestamp)
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_admin_users_list() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_admin_account();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("users")
.arg("list")
.assert()
.success()
.stdout(
predicates::str::contains("USER ID")
.or(predicates::str::contains("No users found")),
);
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_admin_users_list_with_role() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_admin_account();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("users")
.arg("list")
.arg("--role")
.arg("admin")
.assert()
.success();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("users")
.arg("list")
.arg("--role")
.arg("user")
.assert()
.success();
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_admin_users_list_with_status() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_admin_account();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("users")
.arg("list")
.arg("--status")
.arg("active")
.assert()
.success();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("users")
.arg("list")
.arg("--status")
.arg("disabled")
.assert()
.success();
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_admin_users_list_with_pagination() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_admin_account();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("users")
.arg("list")
.arg("--page")
.arg("1")
.arg("--page-size")
.arg("10")
.assert()
.success()
.stdout(predicates::str::contains("page"));
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_admin_users_show() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_admin_account();
let username = generate_username();
let email = format!("{}@example.com", username);
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
let output = cmd
.arg("admin")
.arg("users")
.arg("create")
.arg(&username)
.arg(&email)
.arg("--password")
.arg("testpass123")
.output()
.unwrap();
let stdout = String::from_utf8_lossy(&output.stdout);
if let Some(user_id) = stdout
.lines()
.find(|line| line.contains("User ID:"))
.and_then(|line| line.split(':').nth(1))
.map(|s| s.trim())
{
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("users")
.arg("show")
.arg(user_id)
.assert()
.success()
.stdout(predicates::str::contains("User Details"))
.stdout(predicates::str::contains("User ID:"))
.stdout(predicates::str::contains("Username:"))
.stdout(predicates::str::contains("Email:"))
.stdout(predicates::str::contains("Role:"))
.stdout(predicates::str::contains("Status:"))
.stdout(predicates::str::contains(&username));
}
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_admin_users_show_json() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_admin_account();
let username = generate_username();
let email = format!("{}@example.com", username);
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
let output = cmd
.arg("admin")
.arg("users")
.arg("create")
.arg(&username)
.arg(&email)
.arg("--password")
.arg("testpass123")
.output()
.unwrap();
let stdout = String::from_utf8_lossy(&output.stdout);
if let Some(user_id) = stdout
.lines()
.find(|line| line.contains("User ID:"))
.and_then(|line| line.split(':').nth(1))
.map(|s| s.trim())
{
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("users")
.arg("show")
.arg(user_id)
.arg("--output")
.arg("json")
.assert()
.success()
.stdout(predicates::str::contains("user_id"))
.stdout(predicates::str::contains("username"))
.stdout(predicates::str::contains("email"))
.stdout(predicates::str::contains("role"))
.stdout(predicates::str::contains("status"));
}
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_admin_users_create() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_admin_account();
let username = generate_username();
let email = format!("{}@example.com", username);
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("users")
.arg("create")
.arg(&username)
.arg(&email)
.arg("--password")
.arg("testpass123")
.assert()
.success()
.stdout(predicates::str::contains("User created successfully"))
.stdout(predicates::str::contains("User ID:"))
.stdout(predicates::str::contains(&username));
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_admin_users_create_with_role() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_admin_account();
let username = generate_username();
let email = format!("{}@example.com", username);
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("users")
.arg("create")
.arg(&username)
.arg(&email)
.arg("--role")
.arg("admin")
.arg("--password")
.arg("testpass123")
.assert()
.success()
.stdout(predicates::str::contains("User created successfully"))
.stdout(predicates::str::contains("Role: admin"));
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_admin_users_enable_disable() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_admin_account();
let username = generate_username();
let email = format!("{}@example.com", username);
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
let output = cmd
.arg("admin")
.arg("users")
.arg("create")
.arg(&username)
.arg(&email)
.arg("--password")
.arg("testpass123")
.output()
.unwrap();
let stdout = String::from_utf8_lossy(&output.stdout);
if let Some(user_id) = stdout
.lines()
.find(|line| line.contains("User ID:"))
.and_then(|line| line.split(':').nth(1))
.map(|s| s.trim())
{
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("users")
.arg("disable")
.arg(user_id)
.assert()
.success()
.stdout(predicates::str::contains("User disabled successfully"));
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("users")
.arg("show")
.arg(user_id)
.assert()
.success()
.stdout(predicates::str::contains("Status: disabled"));
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("users")
.arg("enable")
.arg(user_id)
.assert()
.success()
.stdout(predicates::str::contains("User enabled successfully"));
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("users")
.arg("show")
.arg(user_id)
.assert()
.success()
.stdout(predicates::str::contains("Status: active"));
}
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_admin_users_set_role() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_admin_account();
let username = generate_username();
let email = format!("{}@example.com", username);
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
let output = cmd
.arg("admin")
.arg("users")
.arg("create")
.arg(&username)
.arg(&email)
.arg("--password")
.arg("testpass123")
.output()
.unwrap();
let stdout = String::from_utf8_lossy(&output.stdout);
if let Some(user_id) = stdout
.lines()
.find(|line| line.contains("User ID:"))
.and_then(|line| line.split(':').nth(1))
.map(|s| s.trim())
{
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("users")
.arg("set-role")
.arg(user_id)
.arg("admin")
.assert()
.success()
.stdout(predicates::str::contains("User role updated successfully"));
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("users")
.arg("show")
.arg(user_id)
.assert()
.success()
.stdout(predicates::str::contains("Role: admin"));
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("users")
.arg("set-role")
.arg(user_id)
.arg("user")
.assert()
.success()
.stdout(predicates::str::contains("User role updated successfully"));
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("users")
.arg("show")
.arg(user_id)
.assert()
.success()
.stdout(predicates::str::contains("Role: user"));
}
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_admin_stats() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_admin_account();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("stats")
.assert()
.success()
.stdout(predicates::str::contains("System Statistics"))
.stdout(predicates::str::contains("Total Users:"))
.stdout(predicates::str::contains("Total Apps:"))
.stdout(predicates::str::contains("Total App Users:"))
.stdout(predicates::str::contains("Active Connections:"))
.stdout(predicates::str::contains("Total Storage:"));
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_admin_stats_json() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_admin_account();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("stats")
.arg("--output")
.arg("json")
.assert()
.success()
.stdout(predicates::str::contains("total_users"))
.stdout(predicates::str::contains("total_apps"))
.stdout(predicates::str::contains("total_app_users"))
.stdout(predicates::str::contains("active_connections"))
.stdout(predicates::str::contains("total_storage_mb"));
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_admin_operations_not_admin() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_regular_user_account();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("users")
.arg("list")
.assert()
.failure()
.stderr(
predicates::str::contains("Permission denied")
.or(predicates::str::contains("admin role required")),
);
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
async fn test_admin_operations_not_logged_in() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
let config_dir = get_real_config_dir();
fs::create_dir_all(&config_dir).unwrap();
let config = r#"
[server]
url = "http://localhost:28080"
[output]
format = "table"
color = true
[log]
level = "info"
"#;
fs::write(config_dir.join("config.toml"), config).unwrap();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("users")
.arg("list")
.assert()
.failure()
.stderr(
predicates::str::contains("Not logged in")
.or(predicates::str::contains("Admin commands not yet implemented"))
.or(predicates::str::contains("Error"))
);
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_admin_user_lifecycle_workflow() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_admin_account();
let username = generate_username();
let email = format!("{}@example.com", username);
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
let output = cmd
.arg("admin")
.arg("users")
.arg("create")
.arg(&username)
.arg(&email)
.arg("--password")
.arg("testpass123")
.output()
.unwrap();
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
if let Some(user_id) = stdout
.lines()
.find(|line| line.contains("User ID:"))
.and_then(|line| line.split(':').nth(1))
.map(|s| s.trim())
{
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("users")
.arg("list")
.assert()
.success()
.stdout(predicates::str::contains(user_id));
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("users")
.arg("show")
.arg(user_id)
.assert()
.success()
.stdout(predicates::str::contains(&username));
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("users")
.arg("set-role")
.arg(user_id)
.arg("admin")
.assert()
.success();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("users")
.arg("show")
.arg(user_id)
.assert()
.success()
.stdout(predicates::str::contains("Role: admin"));
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("users")
.arg("disable")
.arg(user_id)
.assert()
.success();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("users")
.arg("show")
.arg(user_id)
.assert()
.success()
.stdout(predicates::str::contains("Status: disabled"));
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("users")
.arg("enable")
.arg(user_id)
.assert()
.success();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("users")
.arg("show")
.arg(user_id)
.assert()
.success()
.stdout(predicates::str::contains("Status: active"));
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("stats")
.assert()
.success();
}
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_admin_users_create_invalid_email() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_admin_account();
let username = generate_username();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("users")
.arg("create")
.arg(&username)
.arg("invalid-email")
.arg("--password")
.arg("testpass123")
.assert()
.failure()
.stderr(
predicates::str::contains("Invalid email")
.or(predicates::str::contains("email format")),
);
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_admin_users_create_duplicate_username() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_admin_account();
let username = generate_username();
let email = format!("{}@example.com", username);
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("users")
.arg("create")
.arg(&username)
.arg(&email)
.arg("--password")
.arg("testpass123")
.assert()
.success();
let email2 = format!("{}2@example.com", username);
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("users")
.arg("create")
.arg(&username)
.arg(&email2)
.arg("--password")
.arg("testpass123")
.assert()
.failure()
.stderr(
predicates::str::contains("already exists")
.or(predicates::str::contains("duplicate")),
);
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_admin_users_show_not_found() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_admin_account();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("users")
.arg("show")
.arg("00000000-0000-0000-0000-000000000000")
.assert()
.failure()
.stderr(
predicates::str::contains("not found")
.or(predicates::str::contains("does not exist")),
);
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_admin_users_set_role_invalid() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_admin_account();
let username = generate_username();
let email = format!("{}@example.com", username);
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
let output = cmd
.arg("admin")
.arg("users")
.arg("create")
.arg(&username)
.arg(&email)
.arg("--password")
.arg("testpass123")
.output()
.unwrap();
let stdout = String::from_utf8_lossy(&output.stdout);
if let Some(user_id) = stdout
.lines()
.find(|line| line.contains("User ID:"))
.and_then(|line| line.split(':').nth(1))
.map(|s| s.trim())
{
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("users")
.arg("set-role")
.arg(user_id)
.arg("superadmin")
.assert()
.failure()
.stderr(
predicates::str::contains("Invalid role")
.or(predicates::str::contains("must be 'user' or 'admin'")),
);
}
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_admin_stats_detailed() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_admin_account();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("stats")
.assert()
.success()
.stdout(predicates::str::contains("System Statistics"))
.stdout(predicates::str::contains("Total Users:"))
.stdout(predicates::str::contains("Active Users:"))
.stdout(predicates::str::contains("Disabled Users:"))
.stdout(predicates::str::contains("Total Apps:"))
.stdout(predicates::str::contains("Active Apps:"))
.stdout(predicates::str::contains("Total App Users:"))
.stdout(predicates::str::contains("Active Connections:"))
.stdout(predicates::str::contains("Total Storage:"));
restore_config(backup);
}