Add unit tests for the services
This commit is contained in:
parent
926ac006e7
commit
3b20d12ba1
9 changed files with 231 additions and 12 deletions
86
Cargo.lock
generated
86
Cargo.lock
generated
|
|
@ -36,6 +36,12 @@ version = "0.2.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
|
checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle"
|
||||||
|
version = "1.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "app"
|
name = "app"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
@ -262,6 +268,8 @@ dependencies = [
|
||||||
name = "dao"
|
name = "dao"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"mockall",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
@ -270,6 +278,7 @@ dependencies = [
|
||||||
name = "dao_impl"
|
name = "dao_impl"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
"dao",
|
"dao",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
]
|
]
|
||||||
|
|
@ -303,6 +312,12 @@ version = "0.15.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "downcast"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.11.0"
|
version = "1.11.0"
|
||||||
|
|
@ -383,6 +398,12 @@ dependencies = [
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fragile"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-channel"
|
name = "futures-channel"
|
||||||
version = "0.3.30"
|
version = "0.3.30"
|
||||||
|
|
@ -773,6 +794,33 @@ dependencies = [
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mockall"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "43766c2b5203b10de348ffe19f7e54564b64f3d6018ff7648d1e2d6d3a0f0a48"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"downcast",
|
||||||
|
"fragile",
|
||||||
|
"lazy_static",
|
||||||
|
"mockall_derive",
|
||||||
|
"predicates",
|
||||||
|
"predicates-tree",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mockall_derive"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "af7cbce79ec385a1d4f54baa90a76401eb15d9cab93685f62e7e9f942aa00ae2"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.60",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nom"
|
name = "nom"
|
||||||
version = "7.1.3"
|
version = "7.1.3"
|
||||||
|
|
@ -964,6 +1012,32 @@ version = "0.2.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "predicates"
|
||||||
|
version = "3.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "68b87bfd4605926cdfefc1c3b5f8fe560e3feca9d5552cf68c466d3d8236c7e8"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"predicates-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "predicates-core"
|
||||||
|
version = "1.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "predicates-tree"
|
||||||
|
version = "1.0.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf"
|
||||||
|
dependencies = [
|
||||||
|
"predicates-core",
|
||||||
|
"termtree",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.81"
|
version = "1.0.81"
|
||||||
|
|
@ -1157,16 +1231,22 @@ dependencies = [
|
||||||
name = "service"
|
name = "service"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
"dao",
|
"dao",
|
||||||
|
"mockall",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "service_impl"
|
name = "service_impl"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
"dao",
|
"dao",
|
||||||
|
"mockall",
|
||||||
"service",
|
"service",
|
||||||
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1527,6 +1607,12 @@ dependencies = [
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "termtree"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.59"
|
version = "1.0.59"
|
||||||
|
|
|
||||||
|
|
@ -6,5 +6,7 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
async-trait = "0.1.80"
|
||||||
|
mockall = "0.12.1"
|
||||||
thiserror = "1.0.59"
|
thiserror = "1.0.59"
|
||||||
uuid = "1.8"
|
uuid = "1.8"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
use std::{future::Future, sync::Arc};
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use mockall::automock;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
|
|
@ -8,14 +10,14 @@ pub enum DaoError {
|
||||||
DatabaseQueryError(#[from] Box<dyn std::error::Error>),
|
DatabaseQueryError(#[from] Box<dyn std::error::Error>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[automock]
|
||||||
|
#[async_trait]
|
||||||
pub trait HelloDao {
|
pub trait HelloDao {
|
||||||
fn get_hello(&self) -> impl Future<Output = Result<Arc<str>, DaoError>> + Send;
|
async fn get_hello(&self) -> Result<Arc<str>, DaoError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[automock]
|
||||||
|
#[async_trait]
|
||||||
pub trait PermissionDao {
|
pub trait PermissionDao {
|
||||||
fn has_privilege(
|
async fn has_privilege(&self, user: &str, privilege: &str) -> Result<bool, DaoError>;
|
||||||
&self,
|
|
||||||
user: &str,
|
|
||||||
privilege: &str,
|
|
||||||
) -> impl Future<Output = Result<bool, DaoError>> + Send;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,9 @@ name = "dao_impl"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
async-trait = "0.1.80"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies.sqlx]
|
[dependencies.sqlx]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
use dao::DaoError;
|
use dao::DaoError;
|
||||||
use sqlx::{query, SqlitePool};
|
use sqlx::{query, SqlitePool};
|
||||||
|
|
||||||
|
|
@ -13,6 +14,7 @@ impl HelloDaoImpl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
impl dao::HelloDao for HelloDaoImpl {
|
impl dao::HelloDao for HelloDaoImpl {
|
||||||
async fn get_hello(&self) -> Result<Arc<str>, dao::DaoError> {
|
async fn get_hello(&self) -> Result<Arc<str>, dao::DaoError> {
|
||||||
let result = query!(r"SELECT 'Hello, world!' as message")
|
let result = query!(r"SELECT 'Hello, world!' as message")
|
||||||
|
|
@ -32,6 +34,7 @@ impl PermissionDaoImpl {
|
||||||
Self { pool }
|
Self { pool }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[async_trait]
|
||||||
impl dao::PermissionDao for PermissionDaoImpl {
|
impl dao::PermissionDao for PermissionDaoImpl {
|
||||||
async fn has_privilege(&self, user: &str, privilege: &str) -> Result<bool, dao::DaoError> {
|
async fn has_privilege(&self, user: &str, privilege: &str) -> Result<bool, dao::DaoError> {
|
||||||
let result = query!(
|
let result = query!(
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,10 @@ name = "service"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
async-trait = "0.1.80"
|
||||||
|
mockall = "0.12.1"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies.thiserror]
|
[dependencies.thiserror]
|
||||||
|
|
@ -10,3 +14,6 @@ version = "1.0"
|
||||||
|
|
||||||
[dependencies.dao]
|
[dependencies.dao]
|
||||||
path = "../dao"
|
path = "../dao"
|
||||||
|
|
||||||
|
[dev-dependencies.tokio]
|
||||||
|
version = "1.37.0"
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use mockall::automock;
|
||||||
use std::{future::Future, sync::Arc};
|
use std::{future::Future, sync::Arc};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
|
@ -10,17 +12,19 @@ pub enum ServiceError {
|
||||||
Forbidden,
|
Forbidden,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[automock]
|
||||||
pub trait HelloService {
|
pub trait HelloService {
|
||||||
fn hello(&self) -> impl Future<Output = Result<Arc<str>, ServiceError>> + Send;
|
fn hello(&self) -> impl Future<Output = Result<Arc<str>, ServiceError>> + Send;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[automock]
|
||||||
|
#[async_trait]
|
||||||
pub trait PermissionService {
|
pub trait PermissionService {
|
||||||
fn check_permission(
|
async fn check_permission(&self, privilege: &str) -> Result<(), ServiceError>;
|
||||||
&self,
|
|
||||||
privilege: &str,
|
|
||||||
) -> impl Future<Output = Result<(), ServiceError>> + Send;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[automock]
|
||||||
|
#[async_trait]
|
||||||
pub trait UserService {
|
pub trait UserService {
|
||||||
fn current_user(&self) -> impl Future<Output = Result<Arc<str>, ServiceError>> + Send;
|
async fn current_user(&self) -> Result<Arc<str>, ServiceError>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,10 @@ name = "service_impl"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
async-trait = "0.1.80"
|
||||||
|
mockall = "0.12.1"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies.service]
|
[dependencies.service]
|
||||||
|
|
@ -10,3 +14,7 @@ path = "../service"
|
||||||
|
|
||||||
[dependencies.dao]
|
[dependencies.dao]
|
||||||
path = "../dao"
|
path = "../dao"
|
||||||
|
|
||||||
|
[dev-dependencies.tokio]
|
||||||
|
version = "1.37.0"
|
||||||
|
features = ["full"]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
|
||||||
pub struct HelloServiceImpl<HelloDao, PermissionService>
|
pub struct HelloServiceImpl<HelloDao, PermissionService>
|
||||||
where
|
where
|
||||||
HelloDao: dao::HelloDao + Sync + Send,
|
HelloDao: dao::HelloDao + Sync + Send,
|
||||||
|
|
@ -54,6 +56,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
impl<PermissionDao, UserService> service::PermissionService
|
impl<PermissionDao, UserService> service::PermissionService
|
||||||
for PermissionServiceImpl<PermissionDao, UserService>
|
for PermissionServiceImpl<PermissionDao, UserService>
|
||||||
where
|
where
|
||||||
|
|
@ -76,8 +79,109 @@ where
|
||||||
|
|
||||||
pub struct UserServiceDev;
|
pub struct UserServiceDev;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
impl service::UserService for UserServiceDev {
|
impl service::UserService for UserServiceDev {
|
||||||
async fn current_user(&self) -> Result<Arc<str>, service::ServiceError> {
|
async fn current_user(&self) -> Result<Arc<str>, service::ServiceError> {
|
||||||
Ok("DEVUSER".into())
|
Ok("DEVUSER".into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use mockall::predicate::eq;
|
||||||
|
use service::{HelloService, MockPermissionService, PermissionService};
|
||||||
|
use tokio;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_get_hello_successful() {
|
||||||
|
let mut hello_dao = dao::MockHelloDao::new();
|
||||||
|
hello_dao
|
||||||
|
.expect_get_hello()
|
||||||
|
.times(1)
|
||||||
|
.returning(|| Ok("Hello, world!".into()));
|
||||||
|
let mut permission_service = MockPermissionService::new();
|
||||||
|
permission_service
|
||||||
|
.expect_check_permission()
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| Ok(()));
|
||||||
|
|
||||||
|
let hello_service =
|
||||||
|
HelloServiceImpl::new(Arc::new(hello_dao), Arc::new(permission_service));
|
||||||
|
assert_eq!(
|
||||||
|
"Hello, world!",
|
||||||
|
hello_service.hello().await.unwrap().as_ref()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_get_hello_no_permission() {
|
||||||
|
let hello_dao = dao::MockHelloDao::new();
|
||||||
|
|
||||||
|
let mut permission_service = MockPermissionService::new();
|
||||||
|
permission_service
|
||||||
|
.expect_check_permission()
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| Err(service::ServiceError::Forbidden));
|
||||||
|
|
||||||
|
let hello_service =
|
||||||
|
HelloServiceImpl::new(Arc::new(hello_dao), Arc::new(permission_service));
|
||||||
|
if let Err(service::ServiceError::Forbidden) = hello_service.hello().await {
|
||||||
|
// All good
|
||||||
|
} else {
|
||||||
|
panic!("Expected forbidden error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_check_permission() {
|
||||||
|
let mut permission_dao = dao::MockPermissionDao::new();
|
||||||
|
permission_dao
|
||||||
|
.expect_has_privilege()
|
||||||
|
.with(eq("DEVUSER"), eq("hello"))
|
||||||
|
.returning(|_, _| Ok(true));
|
||||||
|
|
||||||
|
let mut user_service = service::MockUserService::new();
|
||||||
|
user_service
|
||||||
|
.expect_current_user()
|
||||||
|
.returning(|| Ok("DEVUSER".into()));
|
||||||
|
|
||||||
|
let permission_service =
|
||||||
|
PermissionServiceImpl::new(Arc::new(permission_dao), Arc::new(user_service));
|
||||||
|
let result = permission_service.check_permission("hello").await;
|
||||||
|
result.expect("Expected successful authorization");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_check_permission_denied() {
|
||||||
|
let mut permission_dao = dao::MockPermissionDao::new();
|
||||||
|
permission_dao
|
||||||
|
.expect_has_privilege()
|
||||||
|
.with(eq("DEVUSER"), eq("hello"))
|
||||||
|
.returning(|_, _| Ok(false));
|
||||||
|
|
||||||
|
let mut user_service = service::MockUserService::new();
|
||||||
|
user_service
|
||||||
|
.expect_current_user()
|
||||||
|
.returning(|| Ok("DEVUSER".into()));
|
||||||
|
|
||||||
|
let permission_service =
|
||||||
|
PermissionServiceImpl::new(Arc::new(permission_dao), Arc::new(user_service));
|
||||||
|
let result = permission_service.check_permission("hello").await;
|
||||||
|
if let Err(service::ServiceError::Forbidden) = result {
|
||||||
|
// All good
|
||||||
|
} else {
|
||||||
|
panic!("Expected forbidden error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_user_service_dev() {
|
||||||
|
use service::UserService;
|
||||||
|
let user_service = UserServiceDev;
|
||||||
|
assert_eq!(
|
||||||
|
"DEVUSER",
|
||||||
|
user_service.current_user().await.unwrap().as_ref()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue