mod common;
use common::{parse_backend_messages, start_test_server, TestClient};
#[tokio::test]
async fn test_sql_syntax_error() {
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 _ = client.read_until_message_type(b'Z').await.expect("Failed to read response");
client.send_query("SELEKT * FROM nowhere").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_error()), "Should receive ErrorResponse for syntax error");
assert!(
messages.iter().any(|m| m.is_ready_for_query()),
"Should be ready for queries after error"
);
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()),
"Connection should still work after error"
);
client.send_terminate().await.expect("Failed to terminate");
server.shutdown();
}
#[tokio::test]
async fn test_nonexistent_table_error() {
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 _ = client.read_until_message_type(b'Z').await.expect("Failed to read response");
client
.send_query("SELECT * FROM table_that_does_not_exist")
.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_error()),
"Should receive ErrorResponse for non-existent table"
);
assert!(messages.iter().any(|m| m.is_ready_for_query()), "Should still be ready for queries");
client.send_terminate().await.expect("Failed to terminate");
server.shutdown();
}
#[tokio::test]
async fn test_invalid_column_error() {
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 _ = client.read_until_message_type(b'Z').await.expect("Failed to read response");
client
.send_query("CREATE TABLE col_test (id INT, name VARCHAR(50))")
.await
.expect("Failed to CREATE");
let create_data = client.read_until_message_type(b'Z').await.expect("Failed to read response");
let create_messages = parse_backend_messages(&create_data);
assert!(
create_messages.iter().any(|m| m.is_command_complete()),
"CREATE TABLE should succeed, got messages: {:?}",
create_messages.iter().map(|m| m.msg_type as char).collect::<Vec<_>>()
);
client
.send_query("SELECT nonexistent_column FROM col_test")
.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_error()),
"Should receive ErrorResponse for invalid column, got messages: {:?}",
messages.iter().map(|m| m.msg_type as char).collect::<Vec<_>>()
);
client.send_terminate().await.expect("Failed to terminate");
server.shutdown();
}
#[tokio::test]
async fn test_multiple_errors_resilience() {
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 _ = client.read_until_message_type(b'Z').await.expect("Failed to read response");
for i in 0..5 {
client.send_query(&format!("INVALID_STATEMENT_{}", i)).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_error()), "Query {} should error", i);
assert!(
messages.iter().any(|m| m.is_ready_for_query()),
"Should still be ready after error {}",
i
);
}
client.send_query("SELECT 1").await.expect("Failed to send valid 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()),
"Valid query should succeed after errors"
);
client.send_terminate().await.expect("Failed to terminate");
server.shutdown();
}
#[tokio::test]
async fn test_constraint_violation_error() {
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 _ = client.read_until_message_type(b'Z').await.expect("Failed to read response");
client
.send_query("CREATE TABLE pk_test (id INT PRIMARY KEY, value INT)")
.await
.expect("Failed to CREATE");
let _ = client.read_until_message_type(b'Z').await.expect("Failed to read response");
client.send_query("INSERT INTO pk_test VALUES (1, 100)").await.expect("Failed to INSERT");
let _ = client.read_until_message_type(b'Z').await.expect("Failed to read response");
client
.send_query("INSERT INTO pk_test VALUES (1, 200)")
.await
.expect("Failed to send duplicate INSERT");
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_ready_for_query()),
"Should be ready for queries after constraint check"
);
client.send_terminate().await.expect("Failed to terminate");
server.shutdown();
}
#[tokio::test]
async fn test_error_recovery() {
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 _ = client.read_until_message_type(b'Z').await.expect("Failed to read response");
client.send_query("CREATE TABLE recovery_test (id INT)").await.expect("Failed to CREATE");
let _ = client.read_until_message_type(b'Z').await.expect("Failed to read response");
client.send_query("SELECT * FROM no_such_table").await.expect("Failed to send bad 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_error()));
client.send_query("INSERT INTO recovery_test VALUES (1)").await.expect("Failed to INSERT");
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()),
"Should complete INSERT after error recovery"
);
client.send_query("SELECT * FROM recovery_test").await.expect("Failed to SELECT");
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_data_row()), "Should return data after error recovery");
client.send_terminate().await.expect("Failed to terminate");
server.shutdown();
}
#[tokio::test]
async fn test_drop_nonexistent_table_error() {
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 _ = client.read_until_message_type(b'Z').await.expect("Failed to read response");
client.send_query("DROP TABLE nonexistent_table").await.expect("Failed to send DROP");
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_error()), "Should error when dropping non-existent table");
assert!(messages.iter().any(|m| m.is_ready_for_query()), "Should still be ready for queries");
client.send_terminate().await.expect("Failed to terminate");
server.shutdown();
}
#[tokio::test]
async fn test_error_message_format() {
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 _ = client.read_until_message_type(b'Z').await.expect("Failed to read response");
client.send_query("INVALID SYNTAX HERE").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);
let error_msg = messages.iter().find(|m| m.is_error());
assert!(error_msg.is_some(), "Should have error message");
let error = error_msg.unwrap();
assert!(!error.payload.is_empty(), "Error payload should not be empty");
client.send_terminate().await.expect("Failed to terminate");
server.shutdown();
}
#[tokio::test]
async fn test_alternating_errors_successes() {
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 _ = client.read_until_message_type(b'Z').await.expect("Failed to read response");
for i in 0..10 {
if i % 2 == 0 {
client.send_query(&format!("SELECT {}", i)).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()),
"Even iteration {} should succeed",
i
);
} else {
client.send_query("INVALID SQL").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_error()), "Odd iteration {} should error", i);
}
}
client.send_terminate().await.expect("Failed to terminate");
server.shutdown();
}
#[tokio::test]
async fn test_type_mismatch_error() {
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 _ = client.read_until_message_type(b'Z').await.expect("Failed to read response");
client.send_query("CREATE TABLE type_test (id INT)").await.expect("Failed to CREATE");
let _ = client.read_until_message_type(b'Z').await.expect("Failed to read response");
client
.send_query("INSERT INTO type_test VALUES ('not an integer')")
.await
.expect("Failed to send INSERT");
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_ready_for_query()),
"Should still be ready for queries after type check"
);
client.send_terminate().await.expect("Failed to terminate");
server.shutdown();
}
#[tokio::test]
async fn test_ambiguous_column_error() {
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 _ = client.read_until_message_type(b'Z').await.expect("Failed to read response");
client.send_query("CREATE TABLE ambig1 (id INT, value INT)").await.expect("Failed to CREATE");
let _ = client.read_until_message_type(b'Z').await.expect("Failed to read response");
client.send_query("CREATE TABLE ambig2 (id INT, value INT)").await.expect("Failed to CREATE");
let _ = client.read_until_message_type(b'Z').await.expect("Failed to read response");
client.send_query("SELECT id FROM ambig1, ambig2").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_ready_for_query()), "Should still be ready for queries");
client.send_terminate().await.expect("Failed to terminate");
server.shutdown();
}
#[tokio::test]
async fn test_concurrent_errors() {
let server = start_test_server().await;
let addr = server.addr();
let handles: Vec<_> = (0..5)
.map(|i| {
tokio::spawn(async move {
let mut client = TestClient::connect(addr).await.expect("Failed to connect");
client
.send_startup(&format!("user{}", i), "testdb")
.await
.expect("Failed to startup");
let _ =
client.read_until_message_type(b'Z').await.expect("Failed to read response");
for j in 0..3 {
client
.send_query(&format!("INVALID_QUERY_{}_{}", i, j))
.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_error()));
}
client.send_query("SELECT 1").await.expect("Failed to send valid 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()),
"Valid query should succeed for client {}",
i
);
client.send_terminate().await.expect("Failed to terminate");
})
})
.collect();
for handle in handles {
handle.await.expect("Client task failed");
}
server.shutdown();
}