use assert_cmd::Command;
use serde_json::{Value, json};
use tempfile::TempDir;
use wiremock::matchers::{method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};
fn write_config(temp: &TempDir, issuer_base: &str) {
let config = format!(
r#"
default_cloud = "qa"
[cloud_auth.qa]
okta_issuer = "{issuer_base}/oauth2/default"
okta_client_id = "test-client"
sm_api_url = "{issuer_base}/api/v1"
capi_url = "{issuer_base}/v1"
"#
);
std::fs::write(temp.path().join("config.toml"), config).unwrap();
}
fn cmd(temp: &TempDir) -> Command {
let mut c = Command::cargo_bin("redisctl").unwrap();
c.env_remove("REDIS_CLOUD_API_KEY");
c.env_remove("REDIS_CLOUD_SECRET_KEY");
c.env_remove("REDISCTL_PROFILE");
c.arg("--config-file").arg(temp.path().join("config.toml"));
c
}
#[tokio::test]
async fn device_login_initiates_without_blocking() {
let temp = TempDir::new().unwrap();
let server = MockServer::start().await;
write_config(&temp, &server.uri());
Mock::given(method("POST"))
.and(path("/oauth2/default/v1/device/authorize"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"device_code": "DEV-CODE-123",
"user_code": "WDJB-MJHT",
"verification_uri": "https://example.test/activate",
"verification_uri_complete": "https://example.test/activate?user_code=WDJB-MJHT",
"expires_in": 600,
"interval": 5
})))
.mount(&server)
.await;
let output = cmd(&temp)
.args([
"cloud",
"auth",
"login",
"--profile",
"qa",
"--device",
"-o",
"json",
])
.assert()
.success()
.get_output()
.clone();
let report: Value = serde_json::from_slice(&output.stdout).expect("stdout is JSON");
assert_eq!(report["status"], "authorization_pending");
assert_eq!(report["user_code"], "WDJB-MJHT");
assert_eq!(
report["verification_uri_complete"],
"https://example.test/activate?user_code=WDJB-MJHT"
);
let pending = temp.path().join("redisctl-pending-qa.json");
assert!(pending.exists(), "pending record should be written");
let saved: Value = serde_json::from_slice(&std::fs::read(&pending).unwrap()).unwrap();
assert!(
saved.to_string().contains("DEV-CODE-123"),
"pending record should carry the device authorization"
);
assert_eq!(saved["profile"], "qa");
}
#[tokio::test]
async fn status_wait_reports_pending_until_approved() {
let temp = TempDir::new().unwrap();
let server = MockServer::start().await;
write_config(&temp, &server.uri());
Mock::given(method("POST"))
.and(path("/oauth2/default/v1/device/authorize"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"device_code": "DEV-CODE-123",
"user_code": "WDJB-MJHT",
"verification_uri": "https://example.test/activate",
"expires_in": 600,
"interval": 1
})))
.mount(&server)
.await;
Mock::given(method("POST"))
.and(path("/oauth2/default/v1/token"))
.respond_with(ResponseTemplate::new(400).set_body_json(json!({
"error": "authorization_pending"
})))
.mount(&server)
.await;
cmd(&temp)
.args([
"cloud",
"auth",
"login",
"--profile",
"qa",
"--device",
"-o",
"json",
])
.assert()
.success();
let output = cmd(&temp)
.args([
"cloud",
"auth",
"status",
"--profile",
"qa",
"--wait",
"--timeout",
"1",
"-o",
"json",
])
.assert()
.success()
.get_output()
.clone();
let report: Value = serde_json::from_slice(&output.stdout).expect("stdout is JSON");
assert_eq!(report["status"], "authorization_pending");
assert_eq!(report["authenticated"], false);
assert!(temp.path().join("redisctl-pending-qa.json").exists());
}