Makefile.toml

TestContainers and diesel test database
This commit is contained in:
Martin Berg Alstad
2024-08-31 12:21:59 +02:00
parent ce770e9c6f
commit 8fb89e0459
19 changed files with 901 additions and 39 deletions

View File

@ -10,7 +10,11 @@ homepage.workspace = true
diesel = { workspace = true }
diesel-async = { workspace = true }
lib = { path = "../../../lib", features = ["diesel", "derive"] }
derive_more = { workspace = true, features = ["constructor", "from"] }
thiserror = { workspace = true }
[dev-dependencies]
tokio = { version = "1.39", features = ["macros"] }
tokio = { workspace = true, features = ["macros"] }
dotenvy_macro = "0.15"
testcontainers-modules = { version = "0.9", features = ["postgres"] }
diesel_async_migrations = "0.14"

9
crates/tests/diesel.toml Normal file
View File

@ -0,0 +1,9 @@
# For documentation on how to configure this file,
# see https://diesel.rs/guides/configuring-diesel-cli
[print_schema]
file = "src/schema.rs"
custom_type_derives = ["diesel::query_builder::QueryId", "Clone"]
[migrations_directory]
dir = "/home/martin/git/rust/lib/crates/tests/migrations"

View File

View File

@ -0,0 +1,6 @@
-- This file was automatically created by Diesel to setup helper functions
-- and other internal bookkeeping. This file is safe to edit, any future
-- changes will be added to existing projects as new migrations.
DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass);
DROP FUNCTION IF EXISTS diesel_set_updated_at();

View File

@ -0,0 +1,36 @@
-- This file was automatically created by Diesel to setup helper functions
-- and other internal bookkeeping. This file is safe to edit, any future
-- changes will be added to existing projects as new migrations.
-- Sets up a trigger for the given table to automatically set a column called
-- `updated_at` whenever the row is modified (unless `updated_at` was included
-- in the modified columns)
--
-- # Example
--
-- ```sql
-- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW());
--
-- SELECT diesel_manage_updated_at('users');
-- ```
CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$
BEGIN
EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s
FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl);
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$
BEGIN
IF (
NEW IS DISTINCT FROM OLD AND
NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at
) THEN
NEW.updated_at := current_timestamp;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;

View File

@ -0,0 +1 @@
DROP TABLE IF EXISTS "user" CASCADE;

View File

@ -0,0 +1,4 @@
CREATE TABLE "user"
(
email VARCHAR(255) PRIMARY KEY
);

View File

@ -1,10 +1,12 @@
use diesel::{AsChangeset, Insertable, Queryable, Selectable};
use diesel_async::{AsyncConnection, AsyncPgConnection};
use dotenvy_macro::dotenv;
use lib::diesel_crud_derive::{
DieselCrudCreate, DieselCrudDelete, DieselCrudList, DieselCrudRead, DieselCrudUpdate,
};
use lib::diesel_crud_trait::DieselCrudCreate;
use test_containers::create_test_containers_pool;
#[cfg(test)]
pub mod test_containers;
diesel::table! {
user (email) {
@ -14,6 +16,8 @@ diesel::table! {
}
#[derive(
Debug,
PartialEq,
Queryable,
Selectable,
Insertable,
@ -39,14 +43,19 @@ struct InsertUser {
#[tokio::test]
async fn test_insert_user() {
let database_url = dotenv!("DATABASE_URL");
let mut conn = AsyncPgConnection::establish(database_url).await.unwrap();
conn.begin_test_transaction().await.unwrap();
let _user = User::insert(
let container = create_test_containers_pool().await.unwrap();
let mut conn = container.pool.get().await.unwrap();
let user = User::insert(
InsertUser {
email: "test".to_string(),
},
&mut conn,
)
.await;
assert_eq!(
user,
Ok(User {
email: "test".to_string()
})
);
}

View File

@ -0,0 +1,48 @@
use derive_more::{Constructor, From};
use diesel_async::pooled_connection::deadpool::{BuildError, PoolError};
use diesel_async::AsyncPgConnection;
use diesel_async_migrations::EmbeddedMigrations;
use lib::diesel::pool::{create_pool_from_url, PgPool};
use testcontainers_modules::postgres::Postgres;
use testcontainers_modules::testcontainers::runners::AsyncRunner;
use testcontainers_modules::testcontainers::{ContainerAsync, TestcontainersError};
/// When the TestContainer is dropped, the container will be removed.
/// # Panics
/// If destructed and the container field is dropped, the container will be removed, and using the pool will cause panic.
#[derive(Constructor)]
pub struct TestContainer {
pub container: ContainerAsync<Postgres>,
pub pool: PgPool,
}
#[derive(Debug, From)]
pub enum ContainerError {
TestContainers(TestcontainersError),
BuildError(BuildError),
PoolError(PoolError),
DieselError(diesel::result::Error),
}
pub async fn create_test_containers_pool<'a>() -> Result<TestContainer, ContainerError> {
let container = create_postgres_container().await?;
let connection_string = format!(
"postgres://postgres:postgres@localhost:{}/postgres",
container.get_host_port_ipv4(5432).await?
);
let pool = create_pool_from_url(connection_string)?;
run_migrations(pool.get().await?.as_mut()).await?;
Ok(TestContainer::new(container, pool))
}
pub(crate) async fn run_migrations(
conn: &mut AsyncPgConnection,
) -> Result<(), diesel::result::Error> {
static EMBEDDED_MIGRATIONS: EmbeddedMigrations =
diesel_async_migrations::embed_migrations!("./migrations");
EMBEDDED_MIGRATIONS.run_pending_migrations(conn).await
}
pub async fn create_postgres_container() -> Result<ContainerAsync<Postgres>, TestcontainersError> {
Postgres::default().start().await
}