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_app_name() -> String {
use std::time::{SystemTime, UNIX_EPOCH};
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
format!("test_app_{}", timestamp)
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_app_create() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_test_account();
let app_name = generate_app_name();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("app")
.arg("create")
.arg(&app_name)
.arg("--default-db")
.arg("main")
.arg("--allowed-dbs")
.arg("main,analytics")
.arg("--max-users")
.arg("100")
.arg("--storage-quota")
.arg("50")
.assert()
.success()
.stdout(predicates::str::contains("App created successfully"))
.stdout(predicates::str::contains("App ID:"))
.stdout(predicates::str::contains("App Token:"))
.stdout(predicates::str::contains(&app_name));
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_app_list() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_test_account();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("app")
.arg("list")
.assert()
.success()
.stdout(predicates::str::contains("APP ID").or(predicates::str::contains("No applications found")));
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_app_list_with_status() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_test_account();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("app")
.arg("list")
.arg("--status")
.arg("active")
.assert()
.success();
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_app_list_with_pagination() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_test_account();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("app")
.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_app_show() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_test_account();
let app_name = generate_app_name();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
let output = cmd
.arg("app")
.arg("create")
.arg(&app_name)
.arg("--default-db")
.arg("main")
.arg("--allowed-dbs")
.arg("main")
.arg("--max-users")
.arg("10")
.arg("--storage-quota")
.arg("10")
.output()
.unwrap();
let stdout = String::from_utf8_lossy(&output.stdout);
if let Some(app_id) = stdout
.lines()
.find(|line| line.contains("App ID:"))
.and_then(|line| line.split(':').nth(1))
.map(|s| s.trim())
{
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("app")
.arg("show")
.arg(app_id)
.assert()
.success()
.stdout(predicates::str::contains("Application Details"))
.stdout(predicates::str::contains("App ID:"))
.stdout(predicates::str::contains("Name:"))
.stdout(predicates::str::contains("Database Configuration:"))
.stdout(predicates::str::contains("Quotas:"))
.stdout(predicates::str::contains(&app_name));
}
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_app_show_json() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_test_account();
let app_name = generate_app_name();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
let output = cmd
.arg("app")
.arg("create")
.arg(&app_name)
.arg("--default-db")
.arg("main")
.arg("--allowed-dbs")
.arg("main")
.arg("--max-users")
.arg("10")
.arg("--storage-quota")
.arg("10")
.output()
.unwrap();
let stdout = String::from_utf8_lossy(&output.stdout);
if let Some(app_id) = stdout
.lines()
.find(|line| line.contains("App ID:"))
.and_then(|line| line.split(':').nth(1))
.map(|s| s.trim())
{
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("app")
.arg("show")
.arg(app_id)
.arg("--output")
.arg("json")
.assert()
.success()
.stdout(predicates::str::contains("app_id"))
.stdout(predicates::str::contains("app_name"))
.stdout(predicates::str::contains("default_database"));
}
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_app_update() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_test_account();
let app_name = generate_app_name();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
let output = cmd
.arg("app")
.arg("create")
.arg(&app_name)
.arg("--default-db")
.arg("main")
.arg("--allowed-dbs")
.arg("main")
.arg("--max-users")
.arg("10")
.arg("--storage-quota")
.arg("10")
.output()
.unwrap();
let stdout = String::from_utf8_lossy(&output.stdout);
if let Some(app_id) = stdout
.lines()
.find(|line| line.contains("App ID:"))
.and_then(|line| line.split(':').nth(1))
.map(|s| s.trim())
{
let new_name = format!("{}_updated", app_name);
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("app")
.arg("update")
.arg(app_id)
.arg("--name")
.arg(&new_name)
.arg("--max-users")
.arg("20")
.assert()
.success()
.stdout(predicates::str::contains("App updated successfully"))
.stdout(predicates::str::contains("Updated Fields:"));
}
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_app_update_multiple_fields() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_test_account();
let app_name = generate_app_name();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
let output = cmd
.arg("app")
.arg("create")
.arg(&app_name)
.arg("--default-db")
.arg("main")
.arg("--allowed-dbs")
.arg("main")
.arg("--max-users")
.arg("10")
.arg("--storage-quota")
.arg("10")
.output()
.unwrap();
let stdout = String::from_utf8_lossy(&output.stdout);
if let Some(app_id) = stdout
.lines()
.find(|line| line.contains("App ID:"))
.and_then(|line| line.split(':').nth(1))
.map(|s| s.trim())
{
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("app")
.arg("update")
.arg(app_id)
.arg("--max-users")
.arg("50")
.arg("--storage-quota")
.arg("100")
.arg("--max-connections")
.arg("20")
.assert()
.success()
.stdout(predicates::str::contains("App updated successfully"));
}
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_app_enable_disable() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_test_account();
let app_name = generate_app_name();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
let output = cmd
.arg("app")
.arg("create")
.arg(&app_name)
.arg("--default-db")
.arg("main")
.arg("--allowed-dbs")
.arg("main")
.arg("--max-users")
.arg("10")
.arg("--storage-quota")
.arg("10")
.output()
.unwrap();
let stdout = String::from_utf8_lossy(&output.stdout);
if let Some(app_id) = stdout
.lines()
.find(|line| line.contains("App ID:"))
.and_then(|line| line.split(':').nth(1))
.map(|s| s.trim())
{
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("app")
.arg("disable")
.arg(app_id)
.assert()
.success()
.stdout(predicates::str::contains("App disabled successfully"));
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("app")
.arg("show")
.arg(app_id)
.assert()
.success()
.stdout(predicates::str::contains("Status: disabled"));
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("app")
.arg("enable")
.arg(app_id)
.assert()
.success()
.stdout(predicates::str::contains("App enabled successfully"));
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("app")
.arg("show")
.arg(app_id)
.assert()
.success()
.stdout(predicates::str::contains("Status: active"));
}
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_app_delete() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_test_account();
let app_name = generate_app_name();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
let output = cmd
.arg("app")
.arg("create")
.arg(&app_name)
.arg("--default-db")
.arg("main")
.arg("--allowed-dbs")
.arg("main")
.arg("--max-users")
.arg("10")
.arg("--storage-quota")
.arg("10")
.output()
.unwrap();
let stdout = String::from_utf8_lossy(&output.stdout);
if let Some(app_id) = stdout
.lines()
.find(|line| line.contains("App ID:"))
.and_then(|line| line.split(':').nth(1))
.map(|s| s.trim())
{
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("app")
.arg("delete")
.arg(app_id)
.arg("--force")
.assert()
.success()
.stdout(predicates::str::contains("App deleted successfully"));
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("app")
.arg("show")
.arg(app_id)
.assert()
.failure();
}
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_app_delete_not_found() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_test_account();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("app")
.arg("delete")
.arg("app_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_app_reset_token() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_test_account();
let app_name = generate_app_name();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
let output = cmd
.arg("app")
.arg("create")
.arg(&app_name)
.arg("--default-db")
.arg("main")
.arg("--allowed-dbs")
.arg("main")
.arg("--max-users")
.arg("10")
.arg("--storage-quota")
.arg("10")
.output()
.unwrap();
let stdout = String::from_utf8_lossy(&output.stdout);
if let Some(app_id) = stdout
.lines()
.find(|line| line.contains("App ID:"))
.and_then(|line| line.split(':').nth(1))
.map(|s| s.trim())
{
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("app")
.arg("reset-token")
.arg(app_id)
.write_stdin("yes\n")
.assert()
.success()
.stdout(predicates::str::contains("App token reset successfully").or(predicates::str::contains("New Token:")));
}
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_app_stats() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_test_account();
let app_name = generate_app_name();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
let output = cmd
.arg("app")
.arg("create")
.arg(&app_name)
.arg("--default-db")
.arg("main")
.arg("--allowed-dbs")
.arg("main")
.arg("--max-users")
.arg("10")
.arg("--storage-quota")
.arg("10")
.output()
.unwrap();
let stdout = String::from_utf8_lossy(&output.stdout);
if let Some(app_id) = stdout
.lines()
.find(|line| line.contains("App ID:"))
.and_then(|line| line.split(':').nth(1))
.map(|s| s.trim())
{
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("app")
.arg("stats")
.arg(app_id)
.assert()
.success()
.stdout(predicates::str::contains("Application Statistics"))
.stdout(predicates::str::contains("Total Users:"))
.stdout(predicates::str::contains("Active Users:"))
.stdout(predicates::str::contains("Total Storage:"))
.stdout(predicates::str::contains("Active Connections:"));
}
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_app_stats_json() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_test_account();
let app_name = generate_app_name();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
let output = cmd
.arg("app")
.arg("create")
.arg(&app_name)
.arg("--default-db")
.arg("main")
.arg("--allowed-dbs")
.arg("main")
.arg("--max-users")
.arg("10")
.arg("--storage-quota")
.arg("10")
.output()
.unwrap();
let stdout = String::from_utf8_lossy(&output.stdout);
if let Some(app_id) = stdout
.lines()
.find(|line| line.contains("App ID:"))
.and_then(|line| line.split(':').nth(1))
.map(|s| s.trim())
{
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("app")
.arg("stats")
.arg(app_id)
.arg("--output")
.arg("json")
.assert()
.success()
.stdout(predicates::str::contains("app_id"))
.stdout(predicates::str::contains("user_count"))
.stdout(predicates::str::contains("total_storage_mb"));
}
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
async fn test_app_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("app")
.arg("list")
.assert()
.failure()
.stderr(
predicates::str::contains("Not logged in")
.or(predicates::str::contains("Config"))
.or(predicates::str::contains("Error"))
);
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_app_lifecycle_workflow() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_test_account();
let app_name = generate_app_name();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
let output = cmd
.arg("app")
.arg("create")
.arg(&app_name)
.arg("--default-db")
.arg("main")
.arg("--allowed-dbs")
.arg("main,analytics")
.arg("--max-users")
.arg("100")
.arg("--storage-quota")
.arg("50")
.output()
.unwrap();
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
if let Some(app_id) = stdout
.lines()
.find(|line| line.contains("App ID:"))
.and_then(|line| line.split(':').nth(1))
.map(|s| s.trim())
{
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("app")
.arg("list")
.assert()
.success()
.stdout(predicates::str::contains(app_id));
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("app")
.arg("show")
.arg(app_id)
.assert()
.success()
.stdout(predicates::str::contains(&app_name));
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("app")
.arg("update")
.arg(app_id)
.arg("--max-users")
.arg("200")
.assert()
.success();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("app")
.arg("disable")
.arg(app_id)
.assert()
.success();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("app")
.arg("enable")
.arg(app_id)
.assert()
.success();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("app")
.arg("stats")
.arg(app_id)
.assert()
.success();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("app")
.arg("delete")
.arg(app_id)
.arg("--force")
.assert()
.success();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("app")
.arg("show")
.arg(app_id)
.assert()
.failure();
}
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_app_create_minimal() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_test_account();
let app_name = generate_app_name();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("app")
.arg("create")
.arg(&app_name)
.arg("--default-db")
.arg("main")
.arg("--allowed-dbs")
.arg("main")
.arg("--max-users")
.arg("10")
.arg("--storage-quota")
.arg("10")
.assert()
.success()
.stdout(predicates::str::contains("App created successfully"));
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_app_create_full() {
let _lock = CONFIG_LOCK.lock().unwrap();
let backup = backup_config();
setup_test_account();
let app_name = generate_app_name();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("app")
.arg("create")
.arg(&app_name)
.arg("--default-db")
.arg("main")
.arg("--allowed-dbs")
.arg("main,analytics,logs")
.arg("--max-users")
.arg("1000")
.arg("--storage-quota")
.arg("500")
.arg("--max-connections")
.arg("50")
.arg("--qps-limit")
.arg("1000")
.assert()
.success()
.stdout(predicates::str::contains("App created successfully"));
restore_config(backup);
}