#![allow(clippy::unwrap_used, clippy::print_stdout, clippy::print_stderr)]
use std::sync::Arc;
use fraiseql_wire::client::FraiseClient;
use tokio::sync::OnceCell;
struct AuthEndpoint {
#[allow(dead_code)]
service: fraiseql_test_support::Service,
user: String,
password: String,
hostport: String,
database: String,
}
impl AuthEndpoint {
fn url_with(&self, user: &str, password: &str) -> String {
format!(
"postgres://{user}:{password}@{}/{}",
self.hostport, self.database
)
}
}
static AUTH_ENDPOINT: OnceCell<Arc<AuthEndpoint>> = OnceCell::const_new();
async fn get_auth_endpoint() -> Arc<AuthEndpoint> {
AUTH_ENDPOINT
.get_or_init(|| async {
let service = fraiseql_test_support::postgres().await.expect(
"DATABASE_URL must be set (or enable fraiseql-test-support/local-testcontainers)",
);
let url = service.url().to_string();
let rest = url
.strip_prefix("postgresql://")
.or_else(|| url.strip_prefix("postgres://"))
.expect("harness url must start with postgres://");
let (userinfo, hostpart) = rest.split_once('@').expect("harness url must contain '@'");
let (user, password) = userinfo.split_once(':').unwrap_or((userinfo, ""));
let (hostport, dbpart) = hostpart.split_once('/').unwrap_or((hostpart, ""));
let database = dbpart.split('?').next().unwrap_or("");
Arc::new(AuthEndpoint {
user: user.to_string(),
password: password.to_string(),
hostport: hostport.to_string(),
database: database.to_string(),
service,
})
})
.await
.clone()
}
#[tokio::test]
async fn test_auth_correct_credentials() {
let e = get_auth_endpoint().await;
let conn_string = e.url_with(&e.user, &e.password);
let result = FraiseClient::connect(&conn_string).await;
assert!(
result.is_ok(),
"should accept correct credentials, got error: {:?}",
result.err()
);
println!("✓ Correct credentials accepted");
}
#[tokio::test]
async fn test_auth_wrong_password_rejected() {
let e = get_auth_endpoint().await;
let conn_string = e.url_with(&e.user, "wrongpassword");
let result = FraiseClient::connect(&conn_string).await;
assert!(result.is_err(), "should reject wrong password");
if let Err(err) = result {
let err_str = err.to_string().to_lowercase();
assert!(
err_str.contains("password")
|| err_str.contains("auth")
|| err_str.contains("failed")
|| err_str.contains("denied"),
"expected auth-related error, got: {}",
err
);
println!("✓ Wrong password rejected with error: {}", err);
}
}
#[tokio::test]
async fn test_auth_wrong_username_rejected() {
let e = get_auth_endpoint().await;
let conn_string = e.url_with("wronguser", &e.password);
let result = FraiseClient::connect(&conn_string).await;
assert!(result.is_err(), "should reject wrong username");
println!("✓ Wrong username rejected");
}
#[tokio::test]
async fn test_auth_empty_password_rejected() {
let e = get_auth_endpoint().await;
let conn_string = e.url_with(&e.user, "");
let result = FraiseClient::connect(&conn_string).await;
assert!(result.is_err(), "should reject empty password");
println!("✓ Empty password rejected");
}
#[tokio::test]
async fn test_auth_multiple_connections() {
let e = get_auth_endpoint().await;
let conn_string = e.url_with(&e.user, &e.password);
for i in 0..5 {
let result = FraiseClient::connect(&conn_string).await;
assert!(
result.is_ok(),
"connection {} should succeed, got error: {:?}",
i + 1,
result.err()
);
}
println!("✓ Multiple sequential connections succeeded");
}
#[tokio::test]
async fn test_auth_success_after_failure() {
let e = get_auth_endpoint().await;
let wrong_conn = e.url_with(&e.user, "wrongpassword");
let result1 = FraiseClient::connect(&wrong_conn).await;
assert!(result1.is_err(), "wrong password should fail");
let correct_conn = e.url_with(&e.user, &e.password);
let result2 = FraiseClient::connect(&correct_conn).await;
assert!(
result2.is_ok(),
"correct password should succeed after failed attempt"
);
println!("✓ Authentication succeeds after previous failure");
}