#![allow(clippy::all, unused_must_use, unused_lifetimes)]
#![cfg(feature = "with-sqlx")]
use sqlx::Row;
use std::future::Future;
use std::pin::Pin;
use testkit_core::{DatabaseBackend, TestDatabaseInstance};
use testkit_core::{DatabaseConfig, DatabasePool, boxed_async, with_boxed_database};
use testkit_postgres::{PostgresError, SqlxConnection, TransactionManager};
use testkit_postgres::{
SqlxPostgresBackend as PostgresBackend,
sqlx_postgres_backend_with_config as postgres_backend_with_config,
};
fn test_config() -> DatabaseConfig {
let admin_url = "postgres://postgres:postgres@postgres:5432/postgres?sslmode=disable";
let user_url = "postgres://postgres:postgres@postgres:5432/postgres?sslmode=disable";
DatabaseConfig::new(admin_url, user_url)
}
async fn test_backend() -> Result<PostgresBackend, PostgresError> {
postgres_backend_with_config(test_config()).await
}
fn is_connection_error(err: &PostgresError) -> bool {
let err_str = err.to_string();
err_str.contains("connection refused")
|| err_str.contains("timeout")
|| err_str.contains("does not exist")
|| err_str.contains("pool timed out")
}
#[allow(dead_code)]
fn boxed_future<T, F, Fut>(
f: F,
) -> impl FnOnce(T) -> Pin<Box<dyn Future<Output = Result<(), PostgresError>> + Send>>
where
F: FnOnce(T) -> Fut + Send + 'static,
Fut: Future<Output = Result<(), PostgresError>> + Send + 'static,
T: Send + 'static,
{
move |t| Box::pin(f(t))
}
#[tokio::test]
async fn test_sqlx_backend() {
let result = test_backend().await;
if let Err(e) = &result {
if is_connection_error(e) {
println!("Skipping test: PostgreSQL appears to be unavailable");
return;
}
}
assert!(result.is_ok(), "Failed to create sqlx backend");
}
#[tokio::test]
async fn test_sqlx_simple_query() {
let backend = match test_backend().await {
Ok(backend) => backend,
Err(e) => {
if is_connection_error(&e) {
println!("Skipping test: PostgreSQL appears to be unavailable");
return;
}
panic!("Failed to create backend: {:?}", e);
}
};
let ctx = match with_boxed_database(backend)
.setup(|conn| {
boxed_async!(async move {
let _create_table =
sqlx::query("CREATE TABLE test_table (id SERIAL PRIMARY KEY, name TEXT)")
.execute(conn.pool_connection())
.await?;
let _insert_data = sqlx::query("INSERT INTO test_table (name) VALUES ($1)")
.bind("test_name")
.execute(conn.pool_connection())
.await?;
Ok(())
})
})
.execute()
.await
{
Ok(ctx) => {
println!("Created test database: {}", ctx.db.name());
ctx
}
Err(e) => {
if is_connection_error(&e) {
println!("Skipping test: PostgreSQL appears to be unavailable");
return;
}
panic!("Failed to create database: {:?}", e);
}
};
let conn = ctx
.db
.pool
.acquire()
.await
.expect("Failed to acquire connection");
let rows = sqlx::query("SELECT * FROM test_table")
.fetch_all(conn.pool_connection())
.await
.expect("Failed to query data");
assert_eq!(rows.len(), 1, "Expected 1 row");
let name: &str = rows[0].get("name");
assert_eq!(name, "test_name", "Expected name to be test_name");
ctx.db
.pool
.release(conn)
.await
.expect("Failed to release connection");
}
#[tokio::test]
async fn test_sqlx_transaction() {
let backend = match test_backend().await {
Ok(backend) => backend,
Err(e) => {
if is_connection_error(&e) {
println!("Skipping test: PostgreSQL appears to be unavailable");
return;
}
panic!("Failed to create backend: {:?}", e);
}
};
let mut ctx = match with_boxed_database(backend)
.setup(|conn| {
boxed_async!(async move {
let _create_table =
sqlx::query("CREATE TABLE test_tx_table (id SERIAL PRIMARY KEY, name TEXT)")
.execute(conn.pool_connection())
.await?;
Ok(())
})
})
.execute()
.await
{
Ok(ctx) => {
println!(
"Created test database for transaction test: {}",
ctx.db.name()
);
ctx
}
Err(e) => {
if is_connection_error(&e) {
println!("Skipping test: PostgreSQL appears to be unavailable");
return;
}
panic!("Failed to create database: {:?}", e);
}
};
let conn = ctx
.db
.pool
.acquire()
.await
.expect("Failed to acquire connection");
let mut tx = <testkit_core::TestDatabaseInstance<PostgresBackend> as TransactionManager>::begin_transaction(&mut ctx.db)
.await
.expect("Failed to begin transaction");
let insert_data = sqlx::query("INSERT INTO test_tx_table (name) VALUES ($1)")
.bind("tx_test_name")
.execute(conn.pool_connection())
.await;
assert!(insert_data.is_ok(), "Failed to insert data in transaction");
<testkit_core::TestDatabaseInstance<PostgresBackend> as TransactionManager>::commit_transaction(&mut tx)
.await
.expect("Failed to commit transaction");
let rows = sqlx::query("SELECT * FROM test_tx_table")
.fetch_all(conn.pool_connection())
.await
.expect("Failed to query data");
assert_eq!(rows.len(), 1, "Expected 1 row");
let name: &str = rows[0].get("name");
assert_eq!(name, "tx_test_name", "Expected name to be tx_test_name");
ctx.db
.pool
.release(conn)
.await
.expect("Failed to release connection");
}
#[tokio::test]
async fn test_sqlx_with_connection() {
let backend = match test_backend().await {
Ok(backend) => backend,
Err(e) => {
if is_connection_error(&e) {
println!("Skipping test: PostgreSQL appears to be unavailable");
return;
}
panic!("Failed to create backend: {:?}", e);
}
};
let datbase_config = test_config();
let backend_clone = backend.clone();
let _test_context = TestDatabaseInstance::new(backend, datbase_config).await;
println!("----- created database -----");
let backend_clone_2 = backend_clone.clone();
let admin_url_connection_string =
"postgres://postgres:postgres@postgres:5432/postgres?sslmode=disable";
let conn = backend_clone_2
.connect_with_string(admin_url_connection_string)
.await
.unwrap();
let conn2 = conn.clone();
let _rows = SqlxConnection::with_connection(admin_url_connection_string, |conn| {
boxed_async!(async move {
sqlx::query("DELETE FROM test_table")
.execute(conn.pool_connection())
.await?;
let insert_query = format!("INSERT INTO test_table (name) VALUES ($1)");
let rows = sqlx::query(&insert_query)
.bind("boxed_test_name")
.execute(conn.pool_connection())
.await?;
Ok::<_, PostgresError>(rows)
})
})
.await
.unwrap();
let rows = sqlx::query("SELECT * FROM test_table")
.fetch_all(conn2.pool_connection())
.await
.unwrap();
assert_eq!(rows.len(), 1);
let name: &str = rows[0].get("name");
assert_eq!(
name, "boxed_test_name",
"Expected name to be boxed_test_name"
);
}
#[tokio::test]
async fn test_sqlx_boxed_api() {
let backend = match test_backend().await {
Ok(backend) => backend,
Err(e) => {
if is_connection_error(&e) {
println!("Skipping test: PostgreSQL appears to be unavailable");
return;
}
panic!("Failed to create backend: {:?}", e);
}
};
let table_name = "boxed_api_test_table";
let ctx = match with_boxed_database(backend)
.setup(|conn| {
let table_name = table_name.to_string();
boxed_async!(async move {
let query = format!(
"CREATE TABLE {} (id SERIAL PRIMARY KEY, name TEXT)",
table_name
);
sqlx::query(&query).execute(conn.pool_connection()).await?;
let insert_query = format!("INSERT INTO {} (name) VALUES ($1)", table_name);
sqlx::query(&insert_query)
.bind("boxed_test_name")
.execute(conn.pool_connection())
.await?;
Ok(())
})
})
.execute()
.await
{
Ok(ctx) => ctx,
Err(e) => {
if is_connection_error(&e) {
println!("Skipping test: PostgreSQL appears to be unavailable");
return;
}
panic!("Failed to create database with boxed API: {:?}", e);
}
};
let conn = ctx
.db
.pool
.acquire()
.await
.expect("Failed to acquire connection");
let query = format!("SELECT * FROM {}", table_name);
let rows = sqlx::query(&query)
.fetch_all(conn.pool_connection())
.await
.expect("Failed to query data");
assert_eq!(rows.len(), 1, "Expected 1 row");
let name: &str = rows[0].get("name");
assert_eq!(
name, "boxed_test_name",
"Expected name to be boxed_test_name"
);
ctx.db
.pool
.release(conn)
.await
.expect("Failed to release connection");
}