Skip to content

Commit

Permalink
Setup integration tests for atmosphere
Browse files Browse the repository at this point in the history
  • Loading branch information
mara-schulke committed Nov 4, 2023
1 parent bf2d02d commit 518f4d3
Show file tree
Hide file tree
Showing 9 changed files with 299 additions and 36 deletions.
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ repository.workspace = true
name = "forest"
path = "examples/forest/main.rs"

[[test]]
name = "integration"
path = "tests/lib.rs"
test = true

[features]
default = ["sqlite", "mysql", "postgres"]
any = ["sqlx/any"]
Expand Down
17 changes: 11 additions & 6 deletions atmosphere-core/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ where
const PRIMARY_KEY: Column<Self>;
const FOREIGN_KEYS: &'static [Column<Self>];
const DATA: &'static [Column<Self>];

fn pk(&self) -> &Self::PrimaryKey;
}

/// A entity is a table that implements [`Create`], [`Read`], [`Update`] & [`Create`]
Expand Down Expand Up @@ -56,6 +58,7 @@ where

self.bind_all(sqlx::query::<sqlx::Postgres>(&query.into_sql()))
.unwrap()
.persistent(false)
.execute(executor)
.await
}
Expand Down Expand Up @@ -140,15 +143,15 @@ where
#[async_trait]
pub trait Update: Table + Send + Sync + Unpin + 'static {
/// Update the row in the database
async fn update<'e, E>(&mut self, executor: E) -> sqlx::Result<PgQueryResult>
async fn update<'e, E>(&self, executor: E) -> sqlx::Result<PgQueryResult>
where
Self: Bind<sqlx::Postgres> + Sync + 'static,
E: sqlx::Executor<'e, Database = sqlx::Postgres>,
for<'q> <sqlx::Postgres as sqlx::database::HasArguments<'q>>::Arguments:
Send + sqlx::IntoArguments<'q, sqlx::Postgres>;

/// Save to the database
async fn save<'e, E>(&mut self, executor: E) -> sqlx::Result<PgQueryResult>
async fn save<'e, E>(&self, executor: E) -> sqlx::Result<PgQueryResult>
where
Self: Bind<sqlx::Postgres> + Sync + 'static,
E: sqlx::Executor<'e, Database = sqlx::Postgres>,
Expand All @@ -161,7 +164,7 @@ impl<T> Update for T
where
T: Table + Send + Sync + Unpin + 'static,
{
async fn update<'e, E>(&mut self, executor: E) -> sqlx::Result<PgQueryResult>
async fn update<'e, E>(&self, executor: E) -> sqlx::Result<PgQueryResult>
where
Self: Bind<sqlx::Postgres>,
E: sqlx::Executor<'e, Database = sqlx::Postgres>,
Expand All @@ -171,10 +174,10 @@ where
let query = crate::runtime::sql::SQL::<T, sqlx::Postgres>::update().into_sql();
let mut query = sqlx::query::<sqlx::Postgres>(&query);
query = self.bind_all(query).unwrap();
query.execute(executor).await
query.persistent(false).execute(executor).await
}

async fn save<'e, E>(&mut self, executor: E) -> sqlx::Result<PgQueryResult>
async fn save<'e, E>(&self, executor: E) -> sqlx::Result<PgQueryResult>
where
Self: Bind<sqlx::Postgres>,
E: sqlx::Executor<'e, Database = sqlx::Postgres>,
Expand All @@ -184,7 +187,7 @@ where
let query = crate::runtime::sql::SQL::<T, sqlx::Postgres>::upsert().into_sql();
let mut query = sqlx::query::<sqlx::Postgres>(&query);
query = self.bind_all(query).unwrap();
query.execute(executor).await
query.persistent(false).execute(executor).await
}
}

Expand Down Expand Up @@ -232,6 +235,7 @@ where

self.bind_all(sqlx::query::<sqlx::Postgres>(&query.into_sql()))
.unwrap()
.persistent(false)
.execute(executor)
.await
}
Expand All @@ -247,6 +251,7 @@ where

sqlx::query::<sqlx::Postgres>(&query.into_sql())
.bind(pk)
.persistent(false)
.execute(executor)
.await
}
Expand Down
176 changes: 171 additions & 5 deletions atmosphere-core/src/testing.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,176 @@
use crate::Entity;
use crate::{Bind, Create, Entity, Read, Update};
use sqlx::PgPool;
use std::fmt::Debug;

/// Automate integration tests for database entities
pub trait Testable: Entity + Eq + Ord + Clone {
fn mock() -> Self;
fn patch(entity: Self) -> Self;
/// Verify creating of entities
pub async fn create<E>(instance: E, pool: &PgPool)
where
E: Create + Read + Bind<sqlx::Postgres> + Clone + Debug + Eq + Send,
{
assert!(E::find(&instance.pk(), pool).await.is_err());

instance.create(pool).await.expect("insertion did not work");

let retrieved = E::find(&instance.pk(), pool)
.await
.expect("instance not found after insertion");

assert_eq!(instance, retrieved);
}

/// Verify read operations
pub async fn read<E>(instance: E, pool: &PgPool)
where
E: Create + Read + Bind<sqlx::Postgres> + Clone + Debug + Eq + Send,
{
assert!(E::find(&instance.pk(), pool).await.is_err());

instance.create(pool).await.expect("insertion did not work");

let retrieved = E::find(&instance.pk(), pool)
.await
.expect("instance not found after insertion");

assert_eq!(instance, retrieved);
}

/// Verify update operations
pub async fn update<E>(mut instance: E, updates: Vec<E>, pool: &PgPool)
where
E: Read + Update + Bind<sqlx::Postgres> + Clone + Debug + Eq + Send,
{
instance.save(pool).await.expect("insertion did not work");

for update in updates {
update
.update(pool)
.await
.expect("updating the instance did not work");

instance
.reload(pool)
.await
.expect("reloading the instance did not work");

assert_eq!(instance, update);

let retrieved = E::find(&instance.pk(), pool)
.await
.expect("instance not found after update");

assert_eq!(instance, retrieved);
}
}

/// Verify delete operations
pub async fn delete<E>(mut instance: E, pool: &PgPool)
where
E: Entity + Bind<sqlx::Postgres> + Clone + Debug + Eq + Send,
{
instance.create(pool).await.expect("insertion did not work");

instance.delete(pool).await.expect("deletion did not work");

instance
.reload(pool)
.await
.expect_err("instance could be reloaded from db after deletion");

println!("until assert");

assert!(E::find(&instance.pk(), pool).await.is_err());

println!("after assert");

instance.create(pool).await.expect("insertion did not work");

println!("pre db");

E::delete_by(instance.pk(), pool)
.await
.expect("deletion did not work");

println!("post db");

instance
.reload(pool)
.await
.expect_err("instance could be reloaded from db after deletion");
}

// TODO: provide helpers to autogenerate uuids, pks, strings, emails, etc – maybe reexport another
// crate?

// Verify ORM interactions of an [`Entity`] against a live database
//pub async fn verify<T: Verifiable + crate::Bind<sqlx::Postgres>>(pool: sqlx::PgPool) {
//let a = T::mock();

//// verify `create`
//{
//a.create(&pool).await.unwrap();
//let db = T::find(a.pk(), &pool).await.unwrap();
//assert_eq!(a, db);
//}

//// verify `reload`
//{
//let mut db = a.clone();
//db.reload(&pool).await.unwrap();
//assert_eq!(a, db)
//}

//// run 256 upserts
//for i in 0..256 {
//let b = T::patch(a.clone(), i);
//assert_ne!(a, b);

//// verify `save`
//{
//b.save(&pool).await.unwrap();
//let mut db = a.clone();
//db.reload(&pool).await.unwrap();
//assert_eq!(b, db);
//let db = T::find(a.pk(), &pool).await.unwrap();
//assert_eq!(a, db);

//// delete randomly to ensure upsert behavior
//if i % 4 == 0 {
//b.delete(&pool).await.unwrap();
//}
//}
//}

//// run 256 updates
//for i in 256..512 {
//let b = T::patch(a.clone(), i);
//assert_ne!(a, b);

//// verify `update`
//{
//b.update(&pool).await.unwrap();
//let mut db = a.clone();
//db.reload(&pool).await.unwrap();
//assert_eq!(b, db);
//let db = T::find(a.pk(), &pool).await.unwrap();
//assert_eq!(a, db);
//}
//}

//// verify `delete`
//{
//let c = T::mock();
//c.create(&pool).await.unwrap();
//assert_eq!(c, T::find(c.pk(), &pool).await.unwrap());
//c.delete(&pool).await.unwrap();
//assert!(T::find(c.pk(), &pool).await.is_err());
//}

//// verify `delete_by`
//{
//let c = T::mock();
//c.create(&pool).await.unwrap();
//assert_eq!(c, T::find(c.pk(), &pool).await.unwrap());
//T::delete_by(c.pk(), &pool).await.unwrap();
//assert!(T::find(c.pk(), &pool).await.is_err());
//}
//}
5 changes: 5 additions & 0 deletions atmosphere-macros/src/schema/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ impl Table {

let schema = schema.to_string();
let pk_ty = &self.primary_key.ty;
let pk_field = &self.primary_key.name;
let primary_key = self.primary_key.quote();
let foreign_keys = self.foreign_keys.iter().map(|r| r.column.quote());
let data = self.data.iter().map(|d| d.quote());
Expand All @@ -94,6 +95,10 @@ impl Table {
const DATA: &'static [::atmosphere::Column<#ident>] = &[
#(#data),*
];

fn pk(&self) -> &Self::PrimaryKey {
&self.#pk_field
}
}
)
}
Expand Down
35 changes: 10 additions & 25 deletions examples/forest/main.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
#![allow(unused)]

use atmosphere::prelude::*;
use atmosphere_core::Table;
use sqlx::{FromRow, PgPool, Postgres};
use sqlx::PgPool;

#[derive(Schema, Debug)]
#[derive(Schema, Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
#[table(name = "forest", schema = "public")]
struct Forest {
#[primary_key]
Expand All @@ -13,7 +11,7 @@ struct Forest {
location: String,
}

#[derive(Schema, Debug)]
#[derive(Schema, Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
#[table(name = "tree", schema = "public")]
#[relation(grouped_by = Forest)]
struct Tree {
Expand All @@ -27,26 +25,13 @@ struct Tree {
async fn main() -> sqlx::Result<()> {
let pool = PgPool::connect(&std::env::var("DATABASE_URL").unwrap()).await?;

let mut forest = Forest {
id: 1,
name: "grunewald".to_owned(),
location: "berlin".to_owned(),
};

forest.delete(&pool).await?;
forest.create(&pool).await?;

dbg!(Forest::find(&1i32, &pool).await?);

forest.name = "test".to_owned();
forest.update(&pool).await?;

dbg!(Forest::find(&1i32, &pool).await?);

forest.name = "test-2".to_owned();
forest.save(&pool).await?;

dbg!(Forest::find(&1i32, &pool).await?);
Forest {
id: 0,
name: "test".to_owned(),
location: "germany".to_owned(),
}
.save(&pool)
.await?;

Ok(())
}
Loading

0 comments on commit 518f4d3

Please sign in to comment.