mod common;
use common::{
parse_backend_messages, start_test_server, start_test_server_with_config,
test_config_with_password, TestClient,
};
use std::io::Write;
use tempfile::NamedTempFile;
use vibesql_server::auth::password::hash_password_argon2;
#[tokio::test]
async fn test_trust_authentication() {
let server = start_test_server().await;
let mut client = TestClient::connect(server.addr()).await.expect("Failed to connect");
client.send_startup("testuser", "testdb").await.expect("Failed to send startup");
let data = client.read_until_message_type(b'Z').await.expect("Failed to read response");
let messages = parse_backend_messages(&data);
assert!(
messages.iter().any(|m| m.is_auth_ok()),
"Trust auth should send AuthenticationOk without password"
);
assert!(
messages.iter().any(|m| m.is_ready_for_query()),
"Should be ready for queries after trust auth"
);
client.send_terminate().await.expect("Failed to terminate");
server.shutdown();
}
#[tokio::test]
async fn test_password_auth_success() {
let mut password_file = NamedTempFile::new().expect("Failed to create temp file");
let hash = hash_password_argon2("secret123").expect("Failed to hash password");
writeln!(password_file, "testuser:{}", hash).expect("Failed to write password file");
password_file.flush().expect("Failed to flush");
let config = test_config_with_password(password_file.path().to_path_buf());
let server = start_test_server_with_config(config).await;
let mut client = TestClient::connect(server.addr()).await.expect("Failed to connect");
client.send_startup("testuser", "testdb").await.expect("Failed to send startup");
let data = client.read_response().await.expect("Failed to read response");
let messages = parse_backend_messages(&data);
assert!(
messages.iter().any(|m| m.is_cleartext_password_request()),
"Should request cleartext password"
);
client.send_password("secret123").await.expect("Failed to send password");
let data = client.read_until_message_type(b'Z').await.expect("Failed to read response");
let messages = parse_backend_messages(&data);
assert!(
messages.iter().any(|m| m.is_auth_ok()),
"Should receive AuthenticationOk after correct password"
);
assert!(
messages.iter().any(|m| m.is_ready_for_query()),
"Should be ready for queries after auth"
);
client.send_query("SELECT 1").await.expect("Failed to send query");
let data = client.read_until_message_type(b'Z').await.expect("Failed to read response");
let messages = parse_backend_messages(&data);
assert!(messages.iter().any(|m| m.is_command_complete()));
client.send_terminate().await.expect("Failed to terminate");
server.shutdown();
}
#[tokio::test]
async fn test_password_auth_failure() {
let mut password_file = NamedTempFile::new().expect("Failed to create temp file");
let hash = hash_password_argon2("correctpassword").expect("Failed to hash password");
writeln!(password_file, "testuser:{}", hash).expect("Failed to write password file");
password_file.flush().expect("Failed to flush");
let config = test_config_with_password(password_file.path().to_path_buf());
let server = start_test_server_with_config(config).await;
let mut client = TestClient::connect(server.addr()).await.expect("Failed to connect");
client.send_startup("testuser", "testdb").await.expect("Failed to send startup");
let _ = client.read_response().await.expect("Failed to read response");
client.send_password("wrongpassword").await.expect("Failed to send password");
let result = client.read_response().await;
match result {
Ok(data) if data.is_empty() => {
}
Ok(_) => {
}
Err(_) => {
}
}
server.shutdown();
}
#[tokio::test]
async fn test_auth_nonexistent_user() {
let mut password_file = NamedTempFile::new().expect("Failed to create temp file");
let hash = hash_password_argon2("secret").expect("Failed to hash password");
writeln!(password_file, "existinguser:{}", hash).expect("Failed to write password file");
password_file.flush().expect("Failed to flush");
let config = test_config_with_password(password_file.path().to_path_buf());
let server = start_test_server_with_config(config).await;
let mut client = TestClient::connect(server.addr()).await.expect("Failed to connect");
client.send_startup("nonexistent", "testdb").await.expect("Failed to send startup");
let _ = client.read_response().await.expect("Failed to read response");
client.send_password("anypassword").await.expect("Failed to send password");
let result = client.read_response().await;
match result {
Ok(data) if data.is_empty() => {
}
Ok(_) | Err(_) => {
}
}
server.shutdown();
}
#[tokio::test]
async fn test_md5_auth() {
let mut password_file = NamedTempFile::new().expect("Failed to create temp file");
writeln!(password_file, "testuser:{{MD5}}secret").expect("Failed to write password file");
password_file.flush().expect("Failed to flush");
let mut config = test_config_with_password(password_file.path().to_path_buf());
config.auth.method = "md5".to_string();
let server = start_test_server_with_config(config).await;
let mut client = TestClient::connect(server.addr()).await.expect("Failed to connect");
client.send_startup("testuser", "testdb").await.expect("Failed to send startup");
let data = client.read_response().await.expect("Failed to read response");
let messages = parse_backend_messages(&data);
let md5_request = messages.iter().find(|m| m.is_md5_password_request());
assert!(md5_request.is_some(), "Should request MD5 password");
let salt = md5_request.unwrap().get_md5_salt().expect("Should have salt");
let md5_response =
vibesql_server::auth::password::compute_md5_password("secret", "testuser", &salt);
client.send_password(&format!("md5{}", md5_response)).await.expect("Failed to send password");
let data = client.read_until_message_type(b'Z').await.expect("Failed to read response");
let messages = parse_backend_messages(&data);
assert!(messages.iter().any(|m| m.is_auth_ok()), "MD5 auth should succeed");
client.send_terminate().await.expect("Failed to terminate");
server.shutdown();
}
#[tokio::test]
async fn test_multiple_users() {
let mut password_file = NamedTempFile::new().expect("Failed to create temp file");
let hash1 = hash_password_argon2("password1").expect("Failed to hash password");
let hash2 = hash_password_argon2("password2").expect("Failed to hash password");
writeln!(password_file, "user1:{}", hash1).expect("Failed to write");
writeln!(password_file, "user2:{}", hash2).expect("Failed to write");
password_file.flush().expect("Failed to flush");
let config = test_config_with_password(password_file.path().to_path_buf());
let server = start_test_server_with_config(config).await;
{
let mut client = TestClient::connect(server.addr()).await.expect("Failed to connect");
client.send_startup("user1", "testdb").await.expect("Failed to send startup");
let _ = client.read_response().await.expect("Failed to read response");
client.send_password("password1").await.expect("Failed to send password");
let data = client.read_until_message_type(b'Z').await.expect("Failed to read response");
let messages = parse_backend_messages(&data);
assert!(messages.iter().any(|m| m.is_auth_ok()), "user1 should auth successfully");
client.send_terminate().await.expect("Failed to terminate");
}
{
let mut client = TestClient::connect(server.addr()).await.expect("Failed to connect");
client.send_startup("user2", "testdb").await.expect("Failed to send startup");
let _ = client.read_response().await.expect("Failed to read response");
client.send_password("password2").await.expect("Failed to send password");
let data = client.read_until_message_type(b'Z').await.expect("Failed to read response");
let messages = parse_backend_messages(&data);
assert!(messages.iter().any(|m| m.is_auth_ok()), "user2 should auth successfully");
client.send_terminate().await.expect("Failed to terminate");
}
server.shutdown();
}
#[tokio::test]
async fn test_password_file_with_comments() {
let mut password_file = NamedTempFile::new().expect("Failed to create temp file");
let hash = hash_password_argon2("mypassword").expect("Failed to hash password");
writeln!(password_file, "# This is a comment").expect("Failed to write");
writeln!(password_file).expect("Failed to write empty line");
writeln!(password_file, "testuser:{}", hash).expect("Failed to write");
writeln!(password_file, "# Another comment").expect("Failed to write");
password_file.flush().expect("Failed to flush");
let config = test_config_with_password(password_file.path().to_path_buf());
let server = start_test_server_with_config(config).await;
let mut client = TestClient::connect(server.addr()).await.expect("Failed to connect");
client.send_startup("testuser", "testdb").await.expect("Failed to send startup");
let _ = client.read_response().await.expect("Failed to read response");
client.send_password("mypassword").await.expect("Failed to send password");
let data = client.read_until_message_type(b'Z').await.expect("Failed to read response");
let messages = parse_backend_messages(&data);
assert!(
messages.iter().any(|m| m.is_auth_ok()),
"Should authenticate despite comments in file"
);
client.send_terminate().await.expect("Failed to terminate");
server.shutdown();
}
#[tokio::test]
async fn test_authenticated_session_identity() {
let mut password_file = NamedTempFile::new().expect("Failed to create temp file");
let hash = hash_password_argon2("secret").expect("Failed to hash password");
writeln!(password_file, "myuser:{}", hash).expect("Failed to write");
password_file.flush().expect("Failed to flush");
let config = test_config_with_password(password_file.path().to_path_buf());
let server = start_test_server_with_config(config).await;
let mut client = TestClient::connect(server.addr()).await.expect("Failed to connect");
client.send_startup("myuser", "mydb").await.expect("Failed to send startup");
let _ = client.read_response().await.expect("Failed to read response");
client.send_password("secret").await.expect("Failed to send password");
let _ = client.read_until_message_type(b'Z').await.expect("Failed to read response");
for _ in 0..5 {
client.send_query("SELECT 1").await.expect("Failed to send query");
let data = client.read_until_message_type(b'Z').await.expect("Failed to read response");
let messages = parse_backend_messages(&data);
assert!(messages.iter().any(|m| m.is_command_complete()));
}
client.send_terminate().await.expect("Failed to terminate");
server.shutdown();
}