//! `pic admins` smoke tests. Verifies the CRUD round trip and the //! capability gate (a Member account can't list/create admins). use predicates::prelude::*; use crate::common; use crate::common::cleanup::UserGuard; #[ignore = "needs DATABASE_URL pointing at a running Postgres"] #[test] fn create_show_list_patch_remove_round_trip() { let Some(fx) = common::fixture_or_skip() else { return; }; let env = common::admin_env(fx); let username = common::unique_username("rtadm"); // Create — password piped via stdin. let out = common::pic_as(&env) .args([ "admins", "create", &username, "--password", "-", "--instance-role", "admin", "--email", "admin@example.test", ]) .write_stdin("pw-from-stdin\n") .output() .expect("admins create"); assert!(out.status.success(), "admins create failed: {out:?}"); let stdout = String::from_utf8(out.stdout).unwrap(); let id = stdout .lines() .find_map(|l| l.strip_prefix("id")) .map(|s| s.trim().to_string()) .expect("id field in admins create output"); let _guard = UserGuard::new(&env.url, &env.token, &id); // List — our new admin appears with the right role. let out = common::pic_as(&env) .args(["admins", "ls"]) .output() .expect("admins ls"); assert!(out.status.success(), "admins ls failed: {out:?}"); let stdout = String::from_utf8(out.stdout).unwrap(); let header = stdout.lines().next().expect("header"); assert_eq!( common::cells(header), vec![ "id", "username", "role", "active", "email", "created_at", "last_login_at" ] ); let row = stdout .lines() .skip(1) .map(common::cells) .find(|c| c.get(1).copied() == Some(username.as_str())) .unwrap_or_else(|| panic!("{username} missing from admins ls: {stdout}")); assert_eq!(row[2], "admin"); assert_eq!(row[3], "true"); // Show — KvBlock includes the username row. common::pic_as(&env) .args(["admins", "show", &id]) .assert() .success() .stdout(predicate::str::contains(&username)); // Patch — deactivate. common::pic_as(&env) .args(["admins", "set", &id, "--active", "false"]) .assert() .success(); // Patch result echoes is_active=false. let out = common::pic_as(&env) .args(["admins", "show", &id]) .output() .expect("admins show after patch"); let stdout = String::from_utf8(out.stdout).unwrap(); assert!( stdout .lines() .any(|l| l.starts_with("is_active") && l.trim_end().ends_with("false")), "patch should have deactivated: {stdout}" ); // Remove. common::pic_as(&env) .args(["admins", "rm", &id]) .assert() .success() .stdout(predicate::str::contains(format!("Deleted admin {id}"))); } #[ignore = "needs DATABASE_URL pointing at a running Postgres"] #[test] fn create_without_password_errors() { let Some(fx) = common::fixture_or_skip() else { return; }; let env = common::admin_env(fx); let username = common::unique_username("nopw"); common::pic_as(&env) .args(["admins", "create", &username]) .assert() .failure() .stderr(predicate::str::contains("missing --password")); } #[ignore = "needs DATABASE_URL pointing at a running Postgres"] #[test] fn member_cannot_list_admins() { let Some(fx) = common::fixture_or_skip() else { return; }; let admin_env = common::admin_env(fx); let m = common::member::member_user(fx, &common::unique_username("nomgmt")); let member_env = common::custom_env(&fx.url, &m.token); common::seed_credentials(&member_env, &m.username); common::pic_as(&member_env) .args(["admins", "ls"]) .assert() .failure() .stderr(predicate::str::contains("HTTP 403")); // Belt and suspenders so the unused `admin_env` doesn't warn. let _ = admin_env; }