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_test_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 = "testuser@localhost"
server = "http://localhost:28080"
username = "testuser"
token = "test_valid_token"
default = true
"#;
fs::write(config_dir.join("config.toml"), config).unwrap();
}
fn generate_user_label() -> 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_user_create() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_test_account();
let user_label = generate_user_label();
let app_id = "app_test123";
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("user")
.arg("create")
.arg(app_id)
.arg("--label")
.arg(&user_label)
.arg("--storage-quota")
.arg("50")
.arg("--max-connections")
.arg("10")
.assert()
.success()
.stdout(predicates::str::contains("User created successfully"))
.stdout(predicates::str::contains("User UID:"))
.stdout(predicates::str::contains("Token:"));
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_user_list() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_test_account();
let app_id = "app_test123";
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("user")
.arg("list")
.arg(app_id)
.assert()
.success()
.stdout(
predicates::str::contains("USER UID")
.or(predicates::str::contains("No users found")),
);
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_user_list_with_status() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_test_account();
let app_id = "app_test123";
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("user")
.arg("list")
.arg(app_id)
.arg("--status")
.arg("active")
.assert()
.success();
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_user_list_with_pagination() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_test_account();
let app_id = "app_test123";
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("user")
.arg("list")
.arg(app_id)
.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_user_show() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_test_account();
let app_id = "app_test123";
let user_label = generate_user_label();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
let output = cmd
.arg("user")
.arg("create")
.arg(app_id)
.arg("--label")
.arg(&user_label)
.arg("--storage-quota")
.arg("10")
.output()
.unwrap();
let stdout = String::from_utf8_lossy(&output.stdout);
if let Some(user_uid) = stdout
.lines()
.find(|line| line.contains("User UID:"))
.and_then(|line| line.split(':').nth(1))
.map(|s| s.trim())
{
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("user")
.arg("show")
.arg(app_id)
.arg(user_uid)
.assert()
.success()
.stdout(predicates::str::contains("User Details"))
.stdout(predicates::str::contains("User UID:"))
.stdout(predicates::str::contains("Label:"))
.stdout(predicates::str::contains("Status:"))
.stdout(predicates::str::contains(&user_label));
}
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_user_show_json() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_test_account();
let app_id = "app_test123";
let user_label = generate_user_label();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
let output = cmd
.arg("user")
.arg("create")
.arg(app_id)
.arg("--label")
.arg(&user_label)
.arg("--storage-quota")
.arg("10")
.output()
.unwrap();
let stdout = String::from_utf8_lossy(&output.stdout);
if let Some(user_uid) = stdout
.lines()
.find(|line| line.contains("User UID:"))
.and_then(|line| line.split(':').nth(1))
.map(|s| s.trim())
{
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("user")
.arg("show")
.arg(app_id)
.arg(user_uid)
.arg("--output")
.arg("json")
.assert()
.success()
.stdout(predicates::str::contains("user_uid"))
.stdout(predicates::str::contains("label"))
.stdout(predicates::str::contains("status"));
}
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_user_update() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_test_account();
let app_id = "app_test123";
let user_label = generate_user_label();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
let output = cmd
.arg("user")
.arg("create")
.arg(app_id)
.arg("--label")
.arg(&user_label)
.arg("--storage-quota")
.arg("10")
.output()
.unwrap();
let stdout = String::from_utf8_lossy(&output.stdout);
if let Some(user_uid) = stdout
.lines()
.find(|line| line.contains("User UID:"))
.and_then(|line| line.split(':').nth(1))
.map(|s| s.trim())
{
let new_label = format!("{}_updated", user_label);
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("user")
.arg("update")
.arg(app_id)
.arg(user_uid)
.arg("--label")
.arg(&new_label)
.arg("--storage-quota")
.arg("20")
.assert()
.success()
.stdout(predicates::str::contains("User updated successfully"))
.stdout(predicates::str::contains("Updated Fields:"));
}
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_user_update_multiple_fields() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_test_account();
let app_id = "app_test123";
let user_label = generate_user_label();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
let output = cmd
.arg("user")
.arg("create")
.arg(app_id)
.arg("--label")
.arg(&user_label)
.arg("--storage-quota")
.arg("10")
.output()
.unwrap();
let stdout = String::from_utf8_lossy(&output.stdout);
if let Some(user_uid) = stdout
.lines()
.find(|line| line.contains("User UID:"))
.and_then(|line| line.split(':').nth(1))
.map(|s| s.trim())
{
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("user")
.arg("update")
.arg(app_id)
.arg(user_uid)
.arg("--storage-quota")
.arg("50")
.arg("--max-connections")
.arg("20")
.arg("--qps-limit")
.arg("100")
.assert()
.success()
.stdout(predicates::str::contains("User updated successfully"));
}
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_user_enable_disable() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_test_account();
let app_id = "app_test123";
let user_label = generate_user_label();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
let output = cmd
.arg("user")
.arg("create")
.arg(app_id)
.arg("--label")
.arg(&user_label)
.arg("--storage-quota")
.arg("10")
.output()
.unwrap();
let stdout = String::from_utf8_lossy(&output.stdout);
if let Some(user_uid) = stdout
.lines()
.find(|line| line.contains("User UID:"))
.and_then(|line| line.split(':').nth(1))
.map(|s| s.trim())
{
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("user")
.arg("disable")
.arg(app_id)
.arg(user_uid)
.assert()
.success()
.stdout(predicates::str::contains("User disabled successfully"));
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("user")
.arg("show")
.arg(app_id)
.arg(user_uid)
.assert()
.success()
.stdout(predicates::str::contains("Status: disabled"));
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("user")
.arg("enable")
.arg(app_id)
.arg(user_uid)
.assert()
.success()
.stdout(predicates::str::contains("User enabled successfully"));
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("user")
.arg("show")
.arg(app_id)
.arg(user_uid)
.assert()
.success()
.stdout(predicates::str::contains("Status: active"));
}
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_user_delete() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_test_account();
let app_id = "app_test123";
let user_label = generate_user_label();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
let output = cmd
.arg("user")
.arg("create")
.arg(app_id)
.arg("--label")
.arg(&user_label)
.arg("--storage-quota")
.arg("10")
.output()
.unwrap();
let stdout = String::from_utf8_lossy(&output.stdout);
if let Some(user_uid) = stdout
.lines()
.find(|line| line.contains("User UID:"))
.and_then(|line| line.split(':').nth(1))
.map(|s| s.trim())
{
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("user")
.arg("delete")
.arg(app_id)
.arg(user_uid)
.arg("--force")
.assert()
.success()
.stdout(predicates::str::contains("User deleted successfully"));
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("user")
.arg("show")
.arg(app_id)
.arg(user_uid)
.assert()
.failure();
}
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_user_delete_not_found() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_test_account();
let app_id = "app_test123";
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("user")
.arg("delete")
.arg(app_id)
.arg("u_nonexistent")
.arg("--force")
.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_user_reset_token() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_test_account();
let app_id = "app_test123";
let user_label = generate_user_label();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
let output = cmd
.arg("user")
.arg("create")
.arg(app_id)
.arg("--label")
.arg(&user_label)
.arg("--storage-quota")
.arg("10")
.output()
.unwrap();
let stdout = String::from_utf8_lossy(&output.stdout);
if let Some(user_uid) = stdout
.lines()
.find(|line| line.contains("User UID:"))
.and_then(|line| line.split(':').nth(1))
.map(|s| s.trim())
{
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("user")
.arg("reset-token")
.arg(app_id)
.arg(user_uid)
.assert()
.success()
.stdout(
predicates::str::contains("User token reset successfully")
.or(predicates::str::contains("New Token:")),
);
}
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_user_stats() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_test_account();
let app_id = "app_test123";
let user_label = generate_user_label();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
let output = cmd
.arg("user")
.arg("create")
.arg(app_id)
.arg("--label")
.arg(&user_label)
.arg("--storage-quota")
.arg("10")
.output()
.unwrap();
let stdout = String::from_utf8_lossy(&output.stdout);
if let Some(user_uid) = stdout
.lines()
.find(|line| line.contains("User UID:"))
.and_then(|line| line.split(':').nth(1))
.map(|s| s.trim())
{
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("user")
.arg("stats")
.arg(app_id)
.arg(user_uid)
.assert()
.success()
.stdout(predicates::str::contains("User Statistics"))
.stdout(predicates::str::contains("Storage Used:"))
.stdout(predicates::str::contains("Connections:"))
.stdout(predicates::str::contains("Last Accessed:"));
}
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_user_stats_json() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_test_account();
let app_id = "app_test123";
let user_label = generate_user_label();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
let output = cmd
.arg("user")
.arg("create")
.arg(app_id)
.arg("--label")
.arg(&user_label)
.arg("--storage-quota")
.arg("10")
.output()
.unwrap();
let stdout = String::from_utf8_lossy(&output.stdout);
if let Some(user_uid) = stdout
.lines()
.find(|line| line.contains("User UID:"))
.and_then(|line| line.split(':').nth(1))
.map(|s| s.trim())
{
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("user")
.arg("stats")
.arg(app_id)
.arg(user_uid)
.arg("--output")
.arg("json")
.assert()
.success()
.stdout(predicates::str::contains("user_uid"))
.stdout(predicates::str::contains("storage_used_mb"))
.stdout(predicates::str::contains("active_connections"));
}
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
async fn test_user_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 app_id = "app_test123";
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("user")
.arg("list")
.arg(app_id)
.assert()
.failure()
.stderr(predicates::str::contains("Not logged in"));
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_user_lifecycle_workflow() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_test_account();
let app_id = "app_test123";
let user_label = generate_user_label();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
let output = cmd
.arg("user")
.arg("create")
.arg(app_id)
.arg("--label")
.arg(&user_label)
.arg("--storage-quota")
.arg("50")
.arg("--max-connections")
.arg("10")
.output()
.unwrap();
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
if let Some(user_uid) = stdout
.lines()
.find(|line| line.contains("User UID:"))
.and_then(|line| line.split(':').nth(1))
.map(|s| s.trim())
{
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("user")
.arg("list")
.arg(app_id)
.assert()
.success()
.stdout(predicates::str::contains(user_uid));
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("user")
.arg("show")
.arg(app_id)
.arg(user_uid)
.assert()
.success()
.stdout(predicates::str::contains(&user_label));
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("user")
.arg("update")
.arg(app_id)
.arg(user_uid)
.arg("--storage-quota")
.arg("100")
.assert()
.success();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("user")
.arg("disable")
.arg(app_id)
.arg(user_uid)
.assert()
.success();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("user")
.arg("enable")
.arg(app_id)
.arg(user_uid)
.assert()
.success();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("user")
.arg("stats")
.arg(app_id)
.arg(user_uid)
.assert()
.success();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("user")
.arg("reset-token")
.arg(app_id)
.arg(user_uid)
.assert()
.success();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("user")
.arg("delete")
.arg(app_id)
.arg(user_uid)
.arg("--force")
.assert()
.success();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("user")
.arg("show")
.arg(app_id)
.arg(user_uid)
.assert()
.failure();
}
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_user_create_minimal() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_test_account();
let app_id = "app_test123";
let user_label = generate_user_label();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("user")
.arg("create")
.arg(app_id)
.arg("--label")
.arg(&user_label)
.assert()
.success()
.stdout(predicates::str::contains("User created successfully"));
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_user_create_full() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_test_account();
let app_id = "app_test123";
let user_label = generate_user_label();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("user")
.arg("create")
.arg(app_id)
.arg("--label")
.arg(&user_label)
.arg("--storage-quota")
.arg("100")
.arg("--max-connections")
.arg("20")
.arg("--qps-limit")
.arg("500")
.assert()
.success()
.stdout(predicates::str::contains("User created successfully"));
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_user_create_with_custom_token() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_test_account();
let app_id = "app_test123";
let user_label = generate_user_label();
let custom_token = format!("tok_custom_{}", generate_user_label());
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("user")
.arg("create")
.arg(app_id)
.arg("--label")
.arg(&user_label)
.arg("--token")
.arg(&custom_token)
.assert()
.success()
.stdout(predicates::str::contains("User created successfully"));
restore_config(backup);
}