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 create_config(accounts: &str) -> String {
format!(
r#"[server]
url = "http://localhost:28080"
[output]
format = "table"
color = true
[log]
level = "info"
{}"#,
accounts
)
}
#[tokio::test]
#[serial(config)]
async fn test_regular_user_cannot_execute_admin_commands() {
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"
[[accounts]]
name = "developer@localhost"
server = "http://localhost:28080"
username = "developer"
token = "dev_token"
default = true
role = "developer"
"#;
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("developer"))
.stderr(predicates::str::contains("admin"))
.stderr(predicates::str::contains("required"));
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
async fn test_admin_can_execute_admin_commands() {
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"
[[accounts]]
name = "admin@localhost"
server = "http://localhost:28080"
username = "admin"
token = "admin_token"
default = true
role = "admin"
"#;
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 yet implemented").or(
predicates::str::contains("Phase 5")
));
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_whoami_displays_admin_role() {
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"
[[accounts]]
name = "admin@localhost"
server = "http://localhost:28080"
username = "admin"
token = "admin_token"
default = true
role = "admin"
"#;
fs::write(config_dir.join("config.toml"), config).unwrap();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("whoami")
.assert()
.success()
.stdout(predicates::str::contains("Role"))
.stdout(predicates::str::contains("admin"));
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_whoami_displays_developer_role() {
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"
[[accounts]]
name = "developer@localhost"
server = "http://localhost:28080"
username = "developer"
token = "dev_token"
default = true
role = "developer"
"#;
fs::write(config_dir.join("config.toml"), config).unwrap();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("whoami")
.assert()
.success()
.stdout(predicates::str::contains("Role"))
.stdout(predicates::str::contains("developer"));
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
async fn test_accounts_list_displays_roles() {
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"
[[accounts]]
name = "admin@localhost"
server = "http://localhost:28080"
username = "admin"
token = "admin_token"
default = true
role = "admin"
[[accounts]]
name = "developer@localhost"
server = "http://localhost:28080"
username = "developer"
token = "dev_token"
default = false
role = "developer"
[[accounts]]
name = "norole@localhost"
server = "http://localhost:28080"
username = "norole"
token = "norole_token"
default = false
role = "user"
"#;
fs::write(config_dir.join("config.toml"), config).unwrap();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("accounts")
.arg("list")
.assert()
.success()
.stdout(predicates::str::contains("admin@localhost"))
.stdout(predicates::str::contains("developer@localhost"))
.stdout(predicates::str::contains("norole@localhost"))
.stdout(predicates::str::contains("admin"))
.stdout(predicates::str::contains("developer"));
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
async fn test_developer_can_execute_app_commands() {
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"
[[accounts]]
name = "developer@localhost"
server = "http://localhost:28080"
username = "developer"
token = "dev_token"
default = true
role = "developer"
"#;
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("Connection refused")
.or(predicates::str::contains("Failed to connect"))
.or(predicates::str::contains("error sending request"))
.or(predicates::str::contains("Resource not found"))
.or(predicates::str::contains("Unauthorized"))
);
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
async fn test_admin_has_all_permissions() {
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"
[[accounts]]
name = "admin@localhost"
server = "http://localhost:28080"
username = "admin"
token = "admin_token"
default = true
role = "admin"
"#;
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("Connection refused")
.or(predicates::str::contains("Failed to connect"))
.or(predicates::str::contains("error sending request"))
.or(predicates::str::contains("Resource not found"))
.or(predicates::str::contains("Unauthorized"))
);
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
async fn test_user_role_cannot_execute_admin_commands() {
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"
[[accounts]]
name = "user@localhost"
server = "http://localhost:28080"
username = "user"
token = "user_token"
default = true
role = "user"
"#;
fs::write(config_dir.join("config.toml"), config).unwrap();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("stats")
.assert()
.failure()
.stderr(predicates::str::contains("user"))
.stderr(predicates::str::contains("admin"))
.stderr(predicates::str::contains("required"));
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
async fn test_multiple_accounts_different_roles() {
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"
[[accounts]]
name = "admin@localhost"
server = "http://localhost:28080"
username = "admin"
token = "admin_token"
default = true
role = "admin"
[[accounts]]
name = "developer@localhost"
server = "http://localhost:28080"
username = "developer"
token = "dev_token"
default = false
role = "developer"
[[accounts]]
name = "user@localhost"
server = "http://localhost:28080"
username = "user"
token = "user_token"
default = false
role = "user"
"#;
fs::write(config_dir.join("config.toml"), config).unwrap();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("stats")
.assert()
.failure()
.stderr(predicates::str::contains("not yet implemented"));
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("accounts")
.arg("switch")
.arg("developer@localhost")
.assert()
.success();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("stats")
.assert()
.failure()
.stderr(predicates::str::contains("developer"))
.stderr(predicates::str::contains("admin"));
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("accounts")
.arg("switch")
.arg("user@localhost")
.assert()
.success();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("stats")
.assert()
.failure()
.stderr(predicates::str::contains("user"))
.stderr(predicates::str::contains("admin"));
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
async fn test_role_persistence() {
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"
[[accounts]]
name = "admin@localhost"
server = "http://localhost:28080"
username = "admin"
token = "admin_token"
default = true
role = "admin"
"#;
fs::write(config_dir.join("config.toml"), config).unwrap();
for _ in 0..3 {
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("accounts")
.arg("list")
.assert()
.success()
.stdout(predicates::str::contains("admin"));
}
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_login_saves_admin_role() {
let backup = backup_config();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("login")
.arg("--username")
.arg("admin")
.arg("--password")
.arg("admin123")
.arg("--server")
.arg("http://localhost:28080")
.assert()
.success()
.stdout(predicates::str::contains("admin"));
let config_path = get_real_config_dir().join("config.toml");
let config_content = fs::read_to_string(&config_path).unwrap();
assert!(config_content.contains("role = \"admin\""));
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_login_saves_developer_role() {
let backup = backup_config();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("login")
.arg("--username")
.arg("developer")
.arg("--password")
.arg("dev123")
.arg("--server")
.arg("http://localhost:28080")
.assert()
.success()
.stdout(predicates::str::contains("developer"));
let config_path = get_real_config_dir().join("config.toml");
let config_content = fs::read_to_string(&config_path).unwrap();
assert!(config_content.contains("role = \"developer\""));
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_login_success_message_shows_role() {
let backup = backup_config();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("login")
.arg("--username")
.arg("admin")
.arg("--password")
.arg("admin123")
.arg("--server")
.arg("http://localhost:28080")
.assert()
.success()
.stdout(predicates::str::contains("Successfully logged in"))
.stdout(predicates::str::contains("Role"))
.stdout(predicates::str::contains("admin"));
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
#[ignore] async fn test_login_different_roles() {
let backup = backup_config();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("login")
.arg("--username")
.arg("admin")
.arg("--password")
.arg("admin123")
.arg("--server")
.arg("http://localhost:28080")
.assert()
.success()
.stdout(predicates::str::contains("admin"));
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("login")
.arg("--username")
.arg("developer")
.arg("--password")
.arg("dev123")
.arg("--server")
.arg("http://localhost:28080")
.assert()
.success()
.stdout(predicates::str::contains("developer"));
let config_path = get_real_config_dir().join("config.toml");
let config_content = fs::read_to_string(&config_path).unwrap();
assert!(config_content.contains("role = \"admin\""));
assert!(config_content.contains("role = \"developer\""));
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
async fn test_admin_users_list_permission() {
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"
url = "http://localhost:28080"
[[accounts]]
name = "developer@localhost"
server = "http://localhost:28080"
username = "developer"
token = "dev_token"
default = true
role = "developer"
"#;
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("developer"))
.stderr(predicates::str::contains("admin"))
.stderr(predicates::str::contains("required"));
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
async fn test_admin_users_show_permission() {
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"
[[accounts]]
name = "developer@localhost"
server = "http://localhost:28080"
username = "developer"
token = "dev_token"
default = true
role = "developer"
"#;
fs::write(config_dir.join("config.toml"), config).unwrap();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("users")
.arg("show")
.arg("some-user-id")
.assert()
.failure()
.stderr(predicates::str::contains("developer"))
.stderr(predicates::str::contains("admin"))
.stderr(predicates::str::contains("required"));
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
async fn test_admin_users_create_permission() {
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"
[[accounts]]
name = "developer@localhost"
server = "http://localhost:28080"
username = "developer"
token = "dev_token"
default = true
role = "developer"
"#;
fs::write(config_dir.join("config.toml"), config).unwrap();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("users")
.arg("create")
.arg("--username")
.arg("newuser")
.arg("--email")
.arg("new@example.com")
.arg("--password")
.arg("pass123")
.assert()
.failure()
.stderr(predicates::str::contains("developer"))
.stderr(predicates::str::contains("admin"))
.stderr(predicates::str::contains("required"));
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
async fn test_admin_users_enable_permission() {
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"
[[accounts]]
name = "developer@localhost"
server = "http://localhost:28080"
username = "developer"
token = "dev_token"
default = true
role = "developer"
"#;
fs::write(config_dir.join("config.toml"), config).unwrap();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("users")
.arg("enable")
.arg("some-user-id")
.assert()
.failure()
.stderr(predicates::str::contains("developer"))
.stderr(predicates::str::contains("admin"))
.stderr(predicates::str::contains("required"));
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
async fn test_admin_users_disable_permission() {
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"
[[accounts]]
name = "developer@localhost"
server = "http://localhost:28080"
username = "developer"
token = "dev_token"
default = true
role = "developer"
"#;
fs::write(config_dir.join("config.toml"), config).unwrap();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("users")
.arg("disable")
.arg("some-user-id")
.assert()
.failure()
.stderr(predicates::str::contains("developer"))
.stderr(predicates::str::contains("admin"))
.stderr(predicates::str::contains("required"));
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
async fn test_admin_users_set_role_permission() {
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"
[[accounts]]
name = "developer@localhost"
server = "http://localhost:28080"
username = "developer"
token = "dev_token"
default = true
role = "developer"
"#;
fs::write(config_dir.join("config.toml"), config).unwrap();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("users")
.arg("set-role")
.arg("some-user-id")
.arg("--role")
.arg("admin")
.assert()
.failure()
.stderr(predicates::str::contains("developer"))
.stderr(predicates::str::contains("admin"))
.stderr(predicates::str::contains("required"));
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
async fn test_admin_stats_permission() {
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"
[[accounts]]
name = "developer@localhost"
server = "http://localhost:28080"
username = "developer"
token = "dev_token"
default = true
role = "developer"
"#;
fs::write(config_dir.join("config.toml"), config).unwrap();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("stats")
.assert()
.failure()
.stderr(predicates::str::contains("developer"))
.stderr(predicates::str::contains("admin"))
.stderr(predicates::str::contains("required"));
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
async fn test_admin_role_passes_all_permission_checks() {
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"
[[accounts]]
name = "admin@localhost"
server = "http://localhost:28080"
username = "admin"
token = "admin_token"
default = true
role = "admin"
"#;
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 yet implemented"));
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("users")
.arg("show")
.arg("user-id")
.assert()
.failure()
.stderr(predicates::str::contains("not yet implemented"));
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("users")
.arg("create")
.arg("--username")
.arg("test")
.arg("--email")
.arg("test@example.com")
.arg("--password")
.arg("pass")
.assert()
.failure()
.stderr(predicates::str::contains("not yet implemented"));
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("users")
.arg("enable")
.arg("user-id")
.assert()
.failure()
.stderr(predicates::str::contains("not yet implemented"));
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("users")
.arg("disable")
.arg("user-id")
.assert()
.failure()
.stderr(predicates::str::contains("not yet implemented"));
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("users")
.arg("set-role")
.arg("user-id")
.arg("--role")
.arg("developer")
.assert()
.failure()
.stderr(predicates::str::contains("not yet implemented"));
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("stats")
.assert()
.failure()
.stderr(predicates::str::contains("not yet implemented"));
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
async fn test_error_message_includes_current_role() {
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"
[[accounts]]
name = "developer@localhost"
server = "http://localhost:28080"
username = "developer"
token = "dev_token"
default = true
role = "developer"
"#;
fs::write(config_dir.join("config.toml"), config).unwrap();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("stats")
.assert()
.failure()
.stderr(predicates::str::contains("Your role: developer"));
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
async fn test_error_message_includes_required_role() {
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"
[[accounts]]
name = "developer@localhost"
server = "http://localhost:28080"
username = "developer"
token = "dev_token"
default = true
role = "developer"
"#;
fs::write(config_dir.join("config.toml"), config).unwrap();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("stats")
.assert()
.failure()
.stderr(predicates::str::contains("required"))
.stderr(predicates::str::contains("admin"))
.stderr(predicates::str::contains("developer"));
restore_config(backup);
}
#[tokio::test]
#[serial(config)]
async fn test_error_message_includes_action_hint() {
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"
[[accounts]]
name = "developer@localhost"
server = "http://localhost:28080"
username = "developer"
token = "dev_token"
default = true
role = "developer"
"#;
fs::write(config_dir.join("config.toml"), config).unwrap();
let mut cmd = Command::cargo_bin("oauth-db").unwrap();
cmd.arg("admin")
.arg("stats")
.assert()
.failure()
.stderr(
predicates::str::contains("switch to an account")
.or(predicates::str::contains("contact administrator"))
);
restore_config(backup);
}