use std::time::Duration;
fn base_url() -> String {
std::env::var("PG_API_TEST_URL").unwrap_or_else(|_| "http://localhost:8580".to_string())
}
fn api_key() -> String {
std::env::var("PG_API_TEST_KEY").unwrap_or_else(|_| "sk_test_key".to_string())
}
fn database() -> String {
std::env::var("PG_API_TEST_DB").unwrap_or_else(|_| "postgres".to_string())
}
fn client() -> reqwest::Client {
reqwest::Client::builder()
.timeout(Duration::from_secs(30))
.build()
.expect("cliente HTTP")
}
async fn query(client: &reqwest::Client, sql: &str, timeout_ms: u64) -> Option<(bool, String)> {
let response = client
.post(format!("{}/v1/query", base_url()))
.header("X-API-Key", api_key())
.json(&serde_json::json!({
"database": database(),
"query": sql,
"options": { "timeout_ms": timeout_ms }
}))
.send()
.await
.ok()?;
let body: serde_json::Value = response.json().await.ok()?;
let success = body
.get("success")
.and_then(|v| v.as_bool())
.unwrap_or(false);
let message = body
.get("error")
.and_then(|e| e.get("message"))
.and_then(|m| m.as_str())
.unwrap_or("")
.to_string();
Some((success, message))
}
#[tokio::test]
async fn erro_de_query_preserva_sqlstate() {
let client = client();
let Some((success, message)) = query(&client, "SELECT coluna_inexistente_hygiene", 5_000).await
else {
println!("⚠️ Servidor não está rodando, pulando teste");
return;
};
assert!(!success, "query inválida deveria falhar");
assert!(
message.contains("SQLSTATE"),
"mensagem sem SQLSTATE (mascarada como 'db error'?): {message}"
);
}
#[tokio::test]
async fn cancelamento_do_cliente_nao_envenena_o_pool() {
let client = client();
if query(&client, "SELECT 1", 5_000).await.is_none() {
println!("⚠️ Servidor não está rodando, pulando teste");
return;
}
let aborting = tokio::time::timeout(
Duration::from_millis(300),
client
.post(format!("{}/v1/query", base_url()))
.header("X-API-Key", api_key())
.json(&serde_json::json!({
"database": database(),
"query": "SELECT pg_sleep(30)",
"options": { "timeout_ms": 3000 }
}))
.send(),
)
.await;
assert!(aborting.is_err(), "a query longa não deveria terminar");
tokio::time::sleep(Duration::from_secs(5)).await;
for attempt in 1..=5 {
let Some((success, message)) = query(&client, "SELECT 1 AS ok", 10_000).await else {
panic!("sem resposta do servidor na tentativa {attempt}");
};
assert!(
success,
"conexão envenenada devolvida ao pool na tentativa {attempt}: {message}"
);
}
}