diff --git a/Cargo.toml b/Cargo.toml index 0e2f461..4cfe09e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ otel = ["dep:opentelemetry", "dep:opentelemetry_sdk", "dep:opentelemetry-otlp", grpc = ["dep:tonic"] testing = ["dep:insta", "dep:rstest", "dep:testcontainers-modules"] test-containers = ["testing", "dep:testcontainers-modules"] -testing-mocks = ["testing", "dep:mockall"] +testing-mocks = ["testing", "dep:mockall", "sea-orm?/mock"] config-yml = ["config/yaml"] [dependencies] @@ -110,10 +110,10 @@ strum_macros = { workspace = true } itertools = { workspace = true } toml = "0.8.0" url = { version = "2.5.0", features = ["serde"] } -uuid = { version = "1.10.0", features = ["v4", "serde"] } +uuid = { workspace = true } futures = "0.3.30" futures-core = "0.3.31" -chrono = { version = "0.4.34", features = ["serde"] } +chrono = { workspace = true, features = ["serde"] } byte-unit = { version = "5.0.0", features = ["serde"] } convert_case = "0.6.0" const_format = "0.2.32" @@ -198,6 +198,8 @@ cargo-manifest = "0.15.0" typed-builder = "0.20.0" rand = "0.8.5" thiserror = "2.0.0" +uuid = { version = "1.10.0", features = ["v4", "serde"] } +chrono = { version = "0.4.34", features = ["serde"] } [package.metadata.docs.rs] # Have docs.rs pass `--all-features` to ensure all features have their documentation built. diff --git a/examples/full/Cargo.toml b/examples/full/Cargo.toml index a410567..1276400 100644 --- a/examples/full/Cargo.toml +++ b/examples/full/Cargo.toml @@ -41,6 +41,13 @@ serde = { workspace = true, features = ["derive"] } lettre = { workspace = true, features = ["pool"] } sendgrid = { workspace = true } +# Other +uuid = { workspace = true, features = ["v7"] } +chrono = { workspace = true, features = ["serde"] } + +[dev-dependencies] +roadster = { version = "0.6", path = "../..", features = ["testing-mocks"] } + [build-dependencies] tonic-build = { workspace = true } vergen = { workspace = true } diff --git a/examples/full/src/model/mod.rs b/examples/full/src/model/mod.rs index fb9369d..8a665b4 100644 --- a/examples/full/src/model/mod.rs +++ b/examples/full/src/model/mod.rs @@ -6,3 +6,5 @@ /// be in the same crate as our application-specific logic in order to use `impl` on the models /// contained in [`entity`]. pub mod entity; + +pub mod user; diff --git a/examples/full/src/model/user.rs b/examples/full/src/model/user.rs new file mode 100644 index 0000000..e41dcfc --- /dev/null +++ b/examples/full/src/model/user.rs @@ -0,0 +1,72 @@ +use crate::model::entity::prelude::User; +use crate::model::entity::user; +use roadster::app::context::ProvideRef; +use roadster::error::RoadsterResult; +use sea_orm::{DatabaseConnection, EntityTrait}; +use uuid::Uuid; + +impl user::Model { + pub async fn find_by_id( + db: impl ProvideRef, + id: Uuid, + ) -> RoadsterResult { + let user = + User::find_by_id(id) + .one(db.provide()) + .await? + .ok_or(sea_orm::DbErr::RecordNotFound(format!( + "User with id {id} not found" + )))?; + + Ok(user) + } +} + +#[cfg(test)] +mod tests { + use crate::model::entity::user; + use chrono::Utc; + use roadster::app::context::MockProvideRef; + use sea_orm::{DatabaseBackend, DatabaseConnection, MockDatabase}; + use uuid::Uuid; + + #[tokio::test] + async fn find_by_id() { + let id = Uuid::now_v7(); + let mut provide_db = MockProvideRef::::new(); + let db = MockDatabase::new(DatabaseBackend::Postgres) + .append_query_results([vec![test_user(id)]]) + .into_connection(); + provide_db.expect_provide().return_const(db); + + let user = user::Model::find_by_id(provide_db, id).await.unwrap(); + + assert_eq!(id, user.id); + assert_eq!(id.to_string(), user.name); + assert_eq!(id.to_string(), user.username); + } + + fn test_user(id: Uuid) -> user::Model { + let now = Utc::now(); + user::Model { + id, + created_at: now.into(), + updated_at: now.into(), + name: id.to_string(), + username: id.to_string(), + email: format!("{id}@example.com"), + password: "password".to_string(), + last_sign_in_at: now.into(), + password_updated_at: now.into(), + email_confirmation_sent_at: None, + email_confirmation_token: None, + email_confirmed_at: None, + recovery_sent_at: None, + recovery_token: None, + email_change_sent_at: None, + email_change_token_new: None, + email_change_token_current: None, + deleted_at: None, + } + } +} diff --git a/src/app/context.rs b/src/app/context.rs index 2feea34..a617a0d 100644 --- a/src/app/context.rs +++ b/src/app/context.rs @@ -247,7 +247,9 @@ impl ProvideRef for AppContext { } } -#[cfg(feature = "db-sql")] +/// Unfortunately, [`Provide`] can not be implemented when the `sea-orm/mock` +/// feature is enabled because `MockDatabase` is not [`Clone`] +#[cfg(all(feature = "db-sql", not(feature = "testing-mocks")))] impl Provide for AppContext { fn provide(&self) -> DatabaseConnection { self.db().clone() diff --git a/src/service/runner.rs b/src/service/runner.rs index 91489e4..bc36c78 100644 --- a/src/service/runner.rs +++ b/src/service/runner.rs @@ -240,7 +240,7 @@ where info!("Received shutdown signal. Shutting down gracefully."); - #[cfg(feature = "db-sql")] + #[cfg(all(feature = "db-sql", not(feature = "testing-mocks")))] let db_close_result = { info!("Closing the DB connection pool."); context.db().clone().close().await @@ -251,7 +251,7 @@ where info!("Running App::graceful_shutdown."); let app_graceful_shutdown_result = app_graceful_shutdown.await; - #[cfg(feature = "db-sql")] + #[cfg(all(feature = "db-sql", not(feature = "testing-mocks")))] db_close_result?; app_graceful_shutdown_result?;