//! Integration tests using browser automation use compact_jwt::{traits::JwsVerifiable, JwsCompact}; use kanidm_client::KanidmClient; use kanidmd_lib::constants::EntryClass; use kanidmd_testkit::login_put_admin_idm_admins; use std::str::FromStr; /// Tries to handle closing the webdriver session if there's an error #[allow(unused_macros)] macro_rules! handle_error { ($client:ident, $e:expr, $msg:expr) => { match $e { Ok(e) => e, Err(e) => { $client.close().await.unwrap(); panic!("{:?}: {:?}", $msg, e); } } }; } /// Tries to get the webdriver client, trying the default chromedriver port if the default selenium port doesn't work #[allow(dead_code)] #[cfg(all(feature = "webdriver", any(test, debug_assertions)))] async fn get_webdriver_client() -> fantoccini::Client { use fantoccini::wd::Capabilities; use serde_json::json; // check if the env var "CI" is set let in_ci = match std::env::var("CI") { Ok(_) => true, Err(_) => false, }; if !in_ci { match fantoccini::ClientBuilder::native() .connect("http://localhost:4444") .await { Ok(val) => val, Err(_) => { // trying the default chromedriver port eprintln!("Couldn't connect on 4444, trying 9515"); fantoccini::ClientBuilder::native() .connect("http://localhost:9515") .await .unwrap() } } } else { println!("In CI setting headless and assuming Chrome"); let cap = json!({ "goog:chromeOptions" : { "args" : ["--headless", "--no-sandbox", "--disable-gpu", "--disable-dev-shm-usage", "--window-size=1280,1024"] } }); let cap: Capabilities = serde_json::from_value(cap).unwrap(); fantoccini::ClientBuilder::native() .capabilities(cap) .connect("http://localhost:9515") .await .unwrap() } } #[kanidmd_testkit::test] #[cfg(feature = "webdriver")] async fn test_webdriver_user_login(rsclient: kanidm_client::KanidmClient) { if !cfg!(feature = "webdriver") { println!("Skipping test as webdriver feature is not enabled!"); return; } use fantoccini::elements::Element; use fantoccini::Locator; use kanidmd_testkit::*; use std::time::Duration; login_put_admin_idm_admins(&rsclient).await; create_user_with_all_attrs( &rsclient, NOT_ADMIN_TEST_USERNAME, Some(NOT_ADMIN_TEST_PASSWORD), ) .await; let c = get_webdriver_client().await; handle_error!( c, c.goto(&rsclient.get_url().to_string()).await, "Couldn't get URL" ); println!("Waiting for page to load"); let mut wait_attempts = 0; loop { tokio::time::sleep(tokio::time::Duration::from_micros(200)).await; c.wait(); if c.find(Locator::Id("username")).await.is_ok() { break; } wait_attempts += 1; if wait_attempts > 10 { panic!("Couldn't find username field after 10 attempts!"); } } let id = handle_error!( c, c.find(Locator::Id("username")).await, "Couldn't find input id=username" ); handle_error!(c, id.click().await, "Couldn't click the username input?"); handle_error!( c, id.send_keys(NOT_ADMIN_TEST_USERNAME).await, "Couldn't type the password?" ); let username_form = handle_error!( c, c.form(Locator::Id("login")).await, "Couldn't find login form" ); handle_error!( c, username_form.submit().await, "Couldn't submit username-login form" ); c.wait(); tokio::time::sleep(Duration::from_millis(300)).await; let password_form = handle_error!( c, c.form(Locator::Id("login")).await, "Coudln't find login form" ); let id = handle_error!( c, c.find(Locator::Id("password")).await, "Couldn't find input id=password" ); handle_error!(c, id.click().await, "Couldn't click the username input?"); handle_error!( c, id.send_keys(NOT_ADMIN_TEST_PASSWORD).await, "Couldn't type the password?" ); handle_error!( c, password_form.submit().await, "Couldn't submit password-login form" ); c.wait(); // try clicking the nav links let mut navlinks: Vec = vec![]; let mut navlinks_attempts = 0; while navlinks.is_empty() { navlinks = handle_error!( c, c.find_all(Locator::Css(".nav-link")).await, "Couldn't find nav-link CSS items" ); navlinks_attempts += 1; tokio::time::sleep(Duration::from_millis(200)).await; if navlinks_attempts > 10 { panic!("Couldn't find navlinks after 2 seconds!"); } } println!("Found navlinks: {:?}", navlinks); for link in navlinks { println!("Clicking {:?}", link.text().await); handle_error!(c, link.click().await, &format!("Couldn't click {:?}", link)); if let Ok(text) = link.text().await { if text.to_lowercase() == "sign out" { println!("looking for the sign out modal to click the cancel button..."); tokio::time::sleep(Duration::from_secs(1)).await; println!("Found the sign out modal, clicking the cancel button"); // find the cancel button and click it let buttons = handle_error!( c, c.find_all(Locator::Css(".btn")).await, "Couldn't find CSS 'btn' items" ); println!("Found the following buttons: {:?}", buttons); for button in buttons { if let Ok(text) = button.text().await { if text == "Cancel" { println!("Found the sign out cancel button, clicking it"); handle_error!(c, button.click().await, "Couldn't click cancel button"); break; } } } } } } // tokio::time::sleep(Duration::from_millis(3000)).await; } #[kanidmd_testkit::test] async fn test_domain_reset_token_key(rsclient: KanidmClient) { login_put_admin_idm_admins(&rsclient).await; let token = rsclient.get_token().await.expect("No bearer token present"); let jwt = JwsCompact::from_str(&token).expect("Failed to parse jwt"); let key_id = jwt.kid().expect("token does not have a key id"); assert!(rsclient.idm_domain_revoke_key(key_id).await.is_ok()); } #[kanidmd_testkit::test] async fn test_idm_domain_set_ldap_basedn(rsclient: KanidmClient) { login_put_admin_idm_admins(&rsclient).await; assert!(rsclient .idm_domain_set_ldap_basedn("dc=krabsarekool,dc=example,dc=com") .await .is_ok()); assert!(rsclient .idm_domain_set_ldap_basedn("krabsarekool") .await .is_err()); } #[kanidmd_testkit::test] async fn test_idm_domain_set_ldap_max_queryable_attrs(rsclient: KanidmClient) { login_put_admin_idm_admins(&rsclient).await; assert!(rsclient .idm_domain_set_ldap_max_queryable_attrs(20) .await .is_ok()); assert!(rsclient .idm_domain_set_ldap_max_queryable_attrs(10) .await .is_ok()); // Ideally this should be "is_err" } #[kanidmd_testkit::test] /// Checks that a built-in group idm_all_persons has the "builtin" class as expected. async fn test_all_persons_has_builtin_class(rsclient: KanidmClient) { login_put_admin_idm_admins(&rsclient).await; let res = rsclient .idm_group_get("idm_all_persons") .await .expect("Failed to get idm_all_persons"); eprintln!("res: {:?}", res); assert!(res .unwrap() .attrs .get("class") .unwrap() .contains(&EntryClass::Builtin.as_ref().into())); } // /// run a test command as the admin user // fn test_cmd_admin(token_cache_path: &str, rsclient: &KanidmClient, cmd: &str) -> Output { // let split_cmd: Vec<&str> = cmd.split_ascii_whitespace().collect(); // test_cmd_admin_split(token_cache_path, rsclient, &split_cmd) // } // /// run a test command as the admin user // fn test_cmd_admin_split(token_cache_path: &str, rsclient: &KanidmClient, cmd: &[&str]) -> Output { // println!( // "##################################\nrunning {}\n##################################", // cmd.join(" ") // ); // let res = cli_kanidm!() // .env("KANIDM_PASSWORD", ADMIN_TEST_PASSWORD) // .args(cmd) // .output() // .unwrap(); // println!("############ result ##################"); // println!("status: {:?}", res.status); // println!("stdout: {}", String::from_utf8_lossy(&res.stdout)); // println!("stderr: {}", String::from_utf8_lossy(&res.stderr)); // println!("######################################"); // assert!(res.status.success()); // res // } // /// run a test command as the idm_admin user // fn test_cmd_idm_admin(token_cache_path: &str, rsclient: &KanidmClient, cmd: &str) -> Output { // println!("##############################\nrunning {}", cmd); // let res = cli_kanidm!() // .env("KANIDM_PASSWORD", IDM_ADMIN_TEST_PASSWORD) // .args(cmd.split(" ")) // .output() // .unwrap(); // println!("##############################\n{} result: {:?}", cmd, res); // assert!(res.status.success()); // res // } // Disabled due to inconsistent test failures and blocking /* #[kanidmd_testkit::test] /// Testing the CLI doing things. async fn test_integration_with_assert_cmd(rsclient: KanidmClient) { // setup the admin things login_put_admin_idm_admins(&rsclient).await; rsclient .idm_person_account_primary_credential_set_password( IDM_ADMIN_TEST_USER, IDM_ADMIN_TEST_PASSWORD, ) .await .expect(&format!("Failed to set {} password", IDM_ADMIN_TEST_USER)); let token_cache_dir = tempdir().unwrap(); let token_cache_path = format!("{}/kanidm_tokens", token_cache_dir.path().display()); // we have to spawn in another thread for ... reasons assert!(tokio::task::spawn_blocking(move || { let anon_login = cli_kanidm!() .args(&["login", "-D", "anonymous"]) .output() .unwrap(); println!("Login Output: {:?}", anon_login); let anon_whoami = cli_kanidm!() .args(&["self", "whoami", "-D", "anonymous"]) .output() .unwrap(); assert!(anon_whoami.status.success()); println!("Output: {:?}", anon_whoami); test_cmd_admin(&token_cache_path, &rsclient, "login -D admin"); // login as idm_admin test_cmd_idm_admin(&token_cache_path, &rsclient, "login -D idm_admin"); test_cmd_admin_split( &token_cache_path, &rsclient, &[ "service-account", "create", NOT_ADMIN_TEST_USERNAME, "Test account", "-D", "admin", "-o", "json", ], ); test_cmd_admin( &token_cache_path, &rsclient, &format!("service-account get -D admin {}", NOT_ADMIN_TEST_USERNAME), ); // updating the display name test_cmd_admin( &token_cache_path, &rsclient, &format!( "service-account update -D admin {} --displayname cheeseballs", NOT_ADMIN_TEST_USERNAME ), ); // updating the email test_cmd_admin( &token_cache_path, &rsclient, &format!( "service-account update -D admin {} --mail foo@bar.com", NOT_ADMIN_TEST_USERNAME ), ); // checking the email was changed let sad = test_cmd_admin( &token_cache_path, &rsclient, &format!( "service-account get -D admin -o json {}", NOT_ADMIN_TEST_USERNAME ), ); let str_output: String = String::from_utf8_lossy(&sad.stdout).into(); assert!(str_output.contains("foo@bar.com")); true }) .await .unwrap()); println!("Success!"); } */