use rustlavel_db::prelude::*;
macro_rules! admin {
($variable:literal) => {
match std::env::var($variable) {
Ok(url) if !url.is_empty() => match Database::connect(&url).await {
Ok(db) => (db, url),
Err(e) => panic!("{} is set but connecting failed: {e}", $variable),
},
_ => {
eprintln!("skipping: {} is not set", $variable);
return;
}
}
};
}
fn as_user(url: &str, user: &str, password: &str) -> String {
let (scheme, rest) = url.split_once("://").expect("a scheme");
let authority = rest.rsplit_once('@').map(|(_, host)| host).unwrap_or(rest);
format!("{scheme}://{user}:{password}@{authority}")
}
#[tokio::test]
async fn postgres_lets_an_open_connection_outlive_its_role() {
let (admin, url) = admin!("REVOCATION_PG_URL");
let (user, password) = ("v_probe_pg", "Probe!2026xyz");
let _ = admin.run(&format!(r#"drop role if exists "{user}""#)).await;
admin
.run(&format!(r#"create role "{user}" with login password '{password}'"#))
.await
.expect("create the role");
let mine = Database::connect(&as_user(&url, user, password)).await.expect("connect");
assert_eq!(mine.scalar::<i64>("select 1", &[]).await.unwrap(), Some(1));
admin.run(&format!(r#"drop role "{user}""#)).await.expect("drop the role");
assert_eq!(
mine.scalar::<i64>("select 1", &[]).await.unwrap(),
Some(1),
"an open connection must keep working: authentication happened at connect time"
);
assert!(
Database::connect(&as_user(&url, user, password)).await.is_err(),
"a new connection with a deleted account must be refused"
);
}
#[tokio::test]
async fn mysql_lets_an_open_connection_outlive_its_user() {
let (admin, url) = admin!("REVOCATION_MYSQL_URL");
let (user, password) = ("v_probe_my", "Probe!2026xyz");
let _ = admin.run(&format!("drop user if exists '{user}'@'%'")).await;
admin
.run(&format!("create user '{user}'@'%' identified by '{password}'"))
.await
.expect("create the user");
let database = url.rsplit('/').next().and_then(|d| d.split('?').next()).unwrap_or("");
admin
.run(&format!("grant select on `{database}`.* to '{user}'@'%'"))
.await
.expect("grant");
let mine = Database::connect(&format!("{}?sslmode=require", as_user(&url, user, password)))
.await
.expect("connect");
assert_eq!(mine.scalar::<i64>("select 1", &[]).await.unwrap(), Some(1));
admin.run(&format!("drop user '{user}'@'%'")).await.expect("drop the user");
assert_eq!(
mine.scalar::<i64>("select 1", &[]).await.unwrap(),
Some(1),
"an open connection must keep working, as on PostgreSQL"
);
assert!(
Database::connect(&format!("{}?sslmode=require", as_user(&url, user, password)))
.await
.is_err(),
"a new connection with a deleted account must be refused"
);
}
#[tokio::test]
async fn sql_server_refuses_to_drop_a_login_that_is_connected() {
let (admin, url) = admin!("REVOCATION_MSSQL_URL");
let (user, password) = ("v_probe_ms", "Probe!2026xyz");
let _ = admin.run(&format!("drop login [{user}]")).await;
admin
.run(&format!("create login [{user}] with password = '{password}'"))
.await
.expect("create the login");
let mine = Database::connect(&as_user(&url, user, password)).await.expect("connect");
assert_eq!(mine.scalar::<i64>("select 1", &[]).await.unwrap(), Some(1));
let refused = admin
.run(&format!("drop login [{user}]"))
.await
.expect_err("SQL Server must refuse while the login is in use");
assert!(refused.to_string().contains("15434"), "got {refused}");
assert!(refused.to_string().contains("currently logged in"), "got {refused}");
let sessions = admin
.select(
&format!("select session_id from sys.dm_exec_sessions where login_name = '{user}'"),
&[],
)
.await
.expect("the session list");
assert!(!sessions.is_empty(), "the connection above should be listed");
for row in &sessions {
let id: i64 = row.get("session_id").expect("a session id");
admin.run(&format!("kill {id}")).await.expect("kill the session");
}
admin.run(&format!("drop login [{user}]")).await.expect("now it drops");
assert!(
mine.scalar::<i64>("select 1", &[]).await.is_err(),
"the killed session must be gone — this is the case the other two do not have"
);
assert!(
Database::connect(&as_user(&url, user, password)).await.is_err(),
"a new connection with a deleted login must be refused"
);
}