mod common;
use common::start_mock_server;
use kaji::client::KeycloakClient;
use kaji::models::AuthenticatorConfigRepresentation;
use kaji::{apply, inspect, plan, validate};
use std::fs;
use std::sync::Arc;
use tempfile::tempdir;
#[tokio::test]
async fn test_authenticator_config_inspect_plan_apply() {
let mock_url = start_mock_server().await;
let mut client = KeycloakClient::new(mock_url);
client.set_target_realm("test-realm".to_string());
client
.login("admin-cli", Some("secret"), None, None)
.await
.expect("Login failed");
let dir = tempdir().unwrap();
let workspace_dir = dir.path().to_path_buf();
inspect::run(
&client,
workspace_dir.clone(),
&["test-realm".to_string()],
true,
)
.await
.expect("Inspect failed");
let realm_dir = workspace_dir.join("test-realm");
assert!(realm_dir.exists());
let config_dir = realm_dir.join("authenticator-configs");
assert!(config_dir.exists());
let mut exported_configs = fs::read_dir(&config_dir).unwrap();
let config_entry = exported_configs.next().unwrap().unwrap();
let config_path = config_entry.path();
assert!(
config_path
.file_name()
.unwrap()
.to_str()
.unwrap()
.starts_with("review profile config")
);
let flows_dir = realm_dir.join("authentication-flows");
assert!(flows_dir.exists());
let flow_path = flows_dir.join("flow-1.yaml");
assert!(flow_path.exists());
let flow_content = fs::read_to_string(&flow_path).unwrap();
assert!(flow_content.contains("authenticatorConfig: review profile config"));
let secrets_file_path = workspace_dir.join(".secrets");
let mut vars = std::collections::HashMap::new();
if secrets_file_path.exists() {
if let Ok(lines) = fs::read_to_string(&secrets_file_path) {
for line in lines.lines() {
if let Some((k, v)) = line.split_once('=') {
let val = v.trim_matches('"').trim_matches('\'').to_string();
vars.insert(k.to_string(), val);
}
}
}
}
let resolver = Arc::new(kaji::utils::secrets::EnvResolver::new(vars))
as Arc<dyn kaji::utils::secrets::SecretResolver>;
let ui = Arc::new(kaji::utils::ui::DialoguerUi::new());
plan::run(
&client,
workspace_dir.clone(),
true,
false,
&["test-realm".to_string()],
ui.clone(),
resolver.clone(),
None,
)
.await
.expect("Plan failed");
let config_file_path = config_path;
let mut config_val: serde_json::Value =
serde_yaml::from_str(&fs::read_to_string(&config_file_path).unwrap()).unwrap();
if let Some(obj) = config_val.as_object_mut() {
if let Some(config_map) = obj.get_mut("config").and_then(|c| c.as_object_mut()) {
config_map.insert("loa-condition-level".to_string(), serde_json::json!("5"));
}
}
fs::write(
&config_file_path,
serde_yaml::to_string(&config_val).unwrap(),
)
.unwrap();
let new_config = AuthenticatorConfigRepresentation {
id: None,
alias: Some("new config".to_string()),
config: Some({
let mut map = std::collections::HashMap::new();
map.insert("some-key".to_string(), serde_json::json!("some-value"));
map
}),
extra: std::collections::HashMap::new(),
};
let new_config_path = config_dir.join("new-config.yaml");
fs::write(
&new_config_path,
serde_yaml::to_string(&new_config).unwrap(),
)
.unwrap();
let mut flow_val: serde_json::Value = serde_yaml::from_str(&flow_content).unwrap();
if let Some(obj) = flow_val.as_object_mut() {
if let Some(execs) = obj
.get_mut("authenticationExecutions")
.and_then(|e| e.as_array_mut())
{
for exec in execs {
if exec.get("authenticator").and_then(|a| a.as_str())
== Some("another-authenticator")
{
if let Some(exec_obj) = exec.as_object_mut() {
exec_obj.insert(
"authenticatorConfig".to_string(),
serde_json::json!("new config"),
);
}
}
}
}
}
fs::write(&flow_path, serde_yaml::to_string(&flow_val).unwrap()).unwrap();
validate::run(workspace_dir.clone(), &["test-realm".to_string()])
.await
.expect("Validation failed");
plan::run(
&client,
workspace_dir.clone(),
true,
false,
&["test-realm".to_string()],
ui.clone(),
resolver.clone(),
None,
)
.await
.expect("Plan after modifications failed");
apply::run(
&client,
workspace_dir.clone(),
&["test-realm".to_string()],
true,
false,
ui,
resolver,
None,
)
.await
.expect("Apply modifications failed");
}
#[tokio::test]
async fn test_authenticator_config_validation_failures() {
let dir = tempdir().unwrap();
let workspace_dir = dir.path().to_path_buf();
let realm_dir = workspace_dir.join("test-realm");
fs::create_dir_all(&realm_dir).unwrap();
fs::write(
realm_dir.join("realm.yaml"),
"realm: test-realm\nenabled: true\n",
)
.unwrap();
let config_dir = realm_dir.join("authenticator-configs");
fs::create_dir_all(&config_dir).unwrap();
fs::write(
config_dir.join("invalid-config.yaml"),
"config:\n key: value\n",
)
.unwrap();
let res = validate::run(workspace_dir, &["test-realm".to_string()]).await;
assert!(res.is_err());
let err_msg = res.unwrap_err().to_string();
assert!(err_msg.contains("alias is missing or empty"));
}