use async_trait::async_trait;
use ecat_data::SqlExecutor;
use ecat_health::HealthCheck;
use std::sync::Arc;
const PROBE_SQL: &str = "SELECT 1";
pub struct RdbmsHealthCheck<C: ?Sized> {
name: String,
client: Arc<C>,
}
impl<C: SqlExecutor + ?Sized> RdbmsHealthCheck<C> {
pub fn new(name: impl Into<String>, client: Arc<C>) -> Self {
Self {
name: name.into(),
client,
}
}
}
#[async_trait]
impl<C: SqlExecutor + ?Sized> HealthCheck for RdbmsHealthCheck<C> {
fn name(&self) -> &str {
&self.name
}
async fn check(&self) -> Result<(), String> {
self.client
.query(PROBE_SQL)
.await
.map(|_| ())
.map_err(|e| e.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::SqlxClient;
use ecat_data::{Dialect, RdbmsError, Row};
use std::sync::Mutex;
struct Recorder {
sql: Mutex<Vec<String>>,
fail: Option<String>,
}
impl Recorder {
fn new(fail: Option<&str>) -> Self {
Self {
sql: Mutex::new(Vec::new()),
fail: fail.map(str::to_string),
}
}
fn last_sql(&self) -> String {
self.sql.lock().unwrap().last().cloned().unwrap_or_default()
}
}
#[async_trait]
impl SqlExecutor for Recorder {
async fn execute(&self, sql: &str) -> Result<u64, RdbmsError> {
self.sql.lock().unwrap().push(sql.to_string());
Err(RdbmsError::Database("execute 不该被探针调用".into()))
}
async fn query(&self, sql: &str) -> Result<Vec<Row>, RdbmsError> {
self.sql.lock().unwrap().push(sql.to_string());
match &self.fail {
Some(msg) => Err(RdbmsError::Connection(msg.clone())),
None => Ok(vec![Row::new(vec!["1".into()], vec![1.into()])]),
}
}
fn dialect(&self) -> Dialect {
Dialect::Standard
}
}
#[tokio::test]
async fn probe_runs_select_1_and_passes() {
let recorder = Arc::new(Recorder::new(None));
let check = RdbmsHealthCheck::new("db", Arc::clone(&recorder));
assert_eq!(check.name(), "db");
assert_eq!(check.check().await, Ok(()));
assert_eq!(recorder.last_sql(), "SELECT 1");
}
#[tokio::test]
async fn failure_surfaces_backend_error_verbatim() {
let recorder = Arc::new(Recorder::new(Some("connection refused")));
let check = RdbmsHealthCheck::new("db", recorder);
let err = check.check().await.unwrap_err();
assert_eq!(err, "connection error: connection refused");
}
#[tokio::test]
async fn real_sqlite_client_is_healthy() {
let client = Arc::new(SqlxClient::connect("sqlite::memory:").await.unwrap());
let check = RdbmsHealthCheck::new("sqlite", client);
assert_eq!(check.check().await, Ok(()));
}
}