From 308b447caa07f441aeceae3b212f5d598c0bdb12 Mon Sep 17 00:00:00 2001 From: Thomas Lohse Date: Mon, 26 Feb 2024 10:19:19 +0100 Subject: [PATCH 1/6] refactored tests --- .github/workflows/build.yml | 27 - .github/workflows/build_artifacts.yml | 36 + .github/workflows/check_format.yml | 25 +- .gitmodules | 5 +- Cargo.toml | 10 +- Ecdar-ProtoBuf | 2 +- ecdar_api_macros/Cargo.toml | 14 - src/api/auth.rs | 143 +- src/api/ecdar_api.rs | 2 +- src/build.rs | 5 +- src/contexts/context_impls/access_context.rs | 398 ++++- src/contexts/context_impls/in_use_context.rs | 275 ++- src/contexts/context_impls/project_context.rs | 413 ++++- src/contexts/context_impls/query_context.rs | 319 +++- src/contexts/context_impls/session_context.rs | 427 ++++- src/contexts/context_impls/user_context.rs | 535 +++++- src/contexts/mod.rs | 123 ++ .../controller_impls/access_controller.rs | 527 +++++- src/controllers/controller_impls/mod.rs | 196 +++ .../controller_impls/project_controller.rs | 1524 ++++++++++++++++- .../controller_impls/query_controller.rs | 599 ++++++- .../controller_impls/session_controller.rs | 342 +++- .../controller_impls/user_controller.rs | 416 ++++- {ecdar_api_macros/src => src}/lib.rs | 2 +- src/main.rs | 1 - src/services/service_impls/reveaal_service.rs | 33 + src/tests/api/auth.rs | 141 -- src/tests/api/mod.rs | 1 - src/tests/contexts/access_context.rs | 393 ----- src/tests/contexts/helpers.rs | 120 -- src/tests/contexts/in_use_context.rs | 271 --- src/tests/contexts/mod.rs | 1 - src/tests/contexts/project_context.rs | 409 ----- src/tests/contexts/query_context.rs | 315 ---- src/tests/contexts/session_context.rs | 422 ----- src/tests/contexts/user_context.rs | 531 ------ src/tests/controllers/access_controller.rs | 523 ------ src/tests/controllers/helpers.rs | 194 --- src/tests/controllers/mod.rs | 1 - src/tests/controllers/project_controller.rs | 1519 ---------------- src/tests/controllers/query_controller.rs | 595 ------- src/tests/controllers/session_controller.rs | 337 ---- src/tests/controllers/user_controller.rs | 412 ----- src/tests/mod.rs | 4 - src/tests/services/mod.rs | 1 - src/tests/services/reveaal_service.rs | 29 - 46 files changed, 6303 insertions(+), 6315 deletions(-) delete mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/build_artifacts.yml delete mode 100644 ecdar_api_macros/Cargo.toml rename {ecdar_api_macros/src => src}/lib.rs (99%) delete mode 100644 src/tests/api/auth.rs delete mode 100644 src/tests/api/mod.rs delete mode 100644 src/tests/contexts/access_context.rs delete mode 100644 src/tests/contexts/helpers.rs delete mode 100644 src/tests/contexts/in_use_context.rs delete mode 100644 src/tests/contexts/mod.rs delete mode 100644 src/tests/contexts/project_context.rs delete mode 100644 src/tests/contexts/query_context.rs delete mode 100644 src/tests/contexts/session_context.rs delete mode 100644 src/tests/contexts/user_context.rs delete mode 100644 src/tests/controllers/access_controller.rs delete mode 100644 src/tests/controllers/helpers.rs delete mode 100644 src/tests/controllers/mod.rs delete mode 100644 src/tests/controllers/project_controller.rs delete mode 100644 src/tests/controllers/query_controller.rs delete mode 100644 src/tests/controllers/session_controller.rs delete mode 100644 src/tests/controllers/user_controller.rs delete mode 100644 src/tests/mod.rs delete mode 100644 src/tests/services/mod.rs delete mode 100644 src/tests/services/reveaal_service.rs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index afc2a04..0000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Build - -on: - push: - pull_request: - branches: [ "main" ] - -env: - CARGO_TERM_COLOR: always - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - submodules: true - - name: Install Protoc - uses: arduino/setup-protoc@v2 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: true - - name: Build - run: cargo build --verbose - diff --git a/.github/workflows/build_artifacts.yml b/.github/workflows/build_artifacts.yml new file mode 100644 index 0000000..7f3c478 --- /dev/null +++ b/.github/workflows/build_artifacts.yml @@ -0,0 +1,36 @@ +name: Build Artifacts + +on: + workflow_dispatch: + push: + +jobs: + build: + strategy: + matrix: + os: [ ubuntu-latest, windows-latest, macos-latest ] + name: Build ${{ matrix.os }} + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v3 + with: + submodules: 'true' + - uses: arduino/setup-protoc@v2 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true + - name: cargo build --release + uses: clechasseur/rs-cargo@v1 + with: + command: build + args: --release + - uses: actions/upload-artifact@v3 + with: + name: reveaal-${{ matrix.os }} + path: ${{ runner.os == 'Windows' && 'target/release/reveaal.exe' || 'target/release/reveaal' }} + if-no-files-found: error + retention-days: 7 diff --git a/.github/workflows/check_format.yml b/.github/workflows/check_format.yml index 83e5ef9..1ace49e 100644 --- a/.github/workflows/check_format.yml +++ b/.github/workflows/check_format.yml @@ -6,13 +6,10 @@ on: jobs: fmt: - name: cargo fmt + name: cargo fmt & Clippy lint and check runs-on: ubuntu-latest steps: - - name: Install Protoc - uses: arduino/setup-protoc@v2 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} + - run: sudo apt-get install llvm protobuf-compiler - uses: actions/checkout@v3 with: submodules: 'true' @@ -27,23 +24,7 @@ jobs: with: command: fmt args: --all -- --check - - clippy: - name: Clippy lint and check - runs-on: ubuntu-latest - steps: - - name: Install Protoc - uses: arduino/setup-protoc@v2 - - uses: actions/checkout@v3 - with: - submodules: 'true' - - uses: dtolnay/rust-toolchain@stable - with: - components: clippy - - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: true - name: clippy --all-targets --all-features uses: clechasseur/rs-clippy-check@v3 with: - args: --all-targets --all-features \ No newline at end of file + args: --all-targets --all-features -- -D warnings diff --git a/.gitmodules b/.gitmodules index 6293847..d5f774f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,4 @@ [submodule "Ecdar-ProtoBuf"] - path = Ecdar-ProtoBuf - url = https://github.com/ECDAR-AAU-SW-P5/Ecdar-ProtoBuf.git + path = Ecdar-ProtoBuf + url = git@github.com:Ecdar/Ecdar-ProtoBuf.git + branch = SW5 # main diff --git a/Cargo.toml b/Cargo.toml index f202e55..015447e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,12 +18,15 @@ authors = [ 'William Woldum' ] +[lib] +name = "ecdar_api" +proc-macro = true + [[bin]] name = "server" path = "src/main.rs" [dependencies] -migration = { path = "migration" } tokio = { version = "1.33.0", features = ["full"] } dotenv = "0.15.0" sea-orm = { version = "^0.12.0", features = ["sqlx-postgres", "runtime-async-std-native-tls", "macros", "tests-cfg", "sqlx-sqlite"] } @@ -40,7 +43,10 @@ regex = "1.10.2" mockall = "0.11.4" bcrypt = "0.15.0" serde_json = "1.0.108" -ecdar_api_macros = { version = "0.1.0", path = "ecdar_api_macros" } +syn = { version = "2.0", features = ["full"] } +quote = "1.0" +convert_case = "0.6.0" +migration = { path = "migration" } thiserror = "1.0.50" [build-dependencies] diff --git a/Ecdar-ProtoBuf b/Ecdar-ProtoBuf index 79d3c1d..f5ae959 160000 --- a/Ecdar-ProtoBuf +++ b/Ecdar-ProtoBuf @@ -1 +1 @@ -Subproject commit 79d3c1d880b9dac488ccb5eb79352409cdc14033 +Subproject commit f5ae9598ebd6de74e17a7a03ae1b0896ae322ef7 diff --git a/ecdar_api_macros/Cargo.toml b/ecdar_api_macros/Cargo.toml deleted file mode 100644 index c9bdfde..0000000 --- a/ecdar_api_macros/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "ecdar_api_macros" -version = "0.1.0" -edition = "2021" - -[lib] -proc-macro = true - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -syn = { version = "2.0", features = ["full"] } -quote = "1.0" -convert_case = "0.6.0" diff --git a/src/api/auth.rs b/src/api/auth.rs index 19de77d..4d80f28 100644 --- a/src/api/auth.rs +++ b/src/api/auth.rs @@ -320,5 +320,144 @@ impl RequestExt for Request { } #[cfg(test)] -#[path = "../tests/api/auth.rs"] -mod tests; +#[cfg(test)] +mod tests { + use crate::api::auth::{RequestExt, Token, TokenError, TokenType}; + use std::{env, str::FromStr}; + use tonic::{metadata::MetadataValue, Request}; + + #[tokio::test] + async fn request_token_trims_bearer() { + let token = "Bearer 1234567890"; + let mut request = Request::new(()); + request + .metadata_mut() + .insert("authorization", MetadataValue::from_str(token).unwrap()); + + let result = request.token_str().unwrap().unwrap(); + + assert_eq!(result, token.trim_start_matches("Bearer ")); + } + + #[tokio::test] + async fn request_token_no_token_returns_none() { + let request = Request::new(()); + let result = request.token_str().unwrap(); + + assert!(result.is_none()); + } + + #[tokio::test] + async fn token_new_access_returns_token() { + env::set_var("ACCESS_TOKEN_HS512_SECRET", "access_secret"); + + let uid = "1"; + let result = Token::new(TokenType::AccessToken, uid); + + assert!(result.is_ok()); + } + + #[tokio::test] + async fn token_new_refresh_returns_token() { + env::set_var("REFRESH_TOKEN_HS512_SECRET", "refresh_secret"); + + let uid = "1"; + let result = Token::new(TokenType::RefreshToken, uid); + + assert!(result.is_ok()); + } + + #[tokio::test] + async fn validate_token_valid_access_returns_tokendata() { + env::set_var("ACCESS_TOKEN_HS512_SECRET", "access_secret"); + + let token = Token::new(TokenType::AccessToken, "1").unwrap(); + let result = token.validate(); + + assert!(result.is_ok()); + } + + #[tokio::test] + async fn validate_token_valid_refresh_returns_tokendata() { + env::set_var("REFRESH_TOKEN_HS512_SECRET", "refresh_secret"); + + let token = Token::new(TokenType::RefreshToken, "1").unwrap(); + let result = token.validate(); + + assert!(result.is_ok()); + } + + #[tokio::test] + async fn validate_token_invalid_returns_err() { + env::set_var("ACCESS_TOKEN_HS512_SECRET", "access_secret"); + env::set_var("REFRESH_TOKEN_HS512_SECRET", "refresh_secret"); + + let result_access = Token::from_str(TokenType::AccessToken, "invalid_token").validate(); + let result_refresh = Token::from_str(TokenType::RefreshToken, "invalid_token").validate(); + + assert_eq!(result_access.unwrap_err(), TokenError::InvalidToken); + assert_eq!(result_refresh.unwrap_err(), TokenError::InvalidToken); + } + + #[tokio::test] + async fn token_type_access_returns_access() { + env::set_var("ACCESS_TOKEN_HS512_SECRET", "access_secret"); + + let token = Token::new(TokenType::AccessToken, "1").unwrap(); + let result = token.token_type(); + + assert_eq!(result, TokenType::AccessToken); + } + + #[tokio::test] + async fn token_type_refresh_returns_refresh() { + env::set_var("REFRESH_TOKEN_HS512_SECRET", "refresh_secret"); + + let token = Token::new(TokenType::RefreshToken, "1").unwrap(); + let result = token.token_type(); + + assert_eq!(result, TokenType::RefreshToken); + } + + #[tokio::test] + async fn token_to_string_returns_string() { + env::set_var("ACCESS_TOKEN_HS512_SECRET", "access_secret"); + + let token = Token::new(TokenType::AccessToken, "1").unwrap(); + let result = token.to_string(); + + assert_eq!(result, token.as_str()); + } + + #[tokio::test] + async fn token_as_str_returns_string() { + env::set_var("ACCESS_TOKEN_HS512_SECRET", "access_secret"); + + let token = Token::new(TokenType::AccessToken, "1").unwrap(); + let result = token.as_str(); + + assert_eq!(result, token.to_string()); + } + + #[tokio::test] + async fn token_from_str_returns_token() { + env::set_var("ACCESS_TOKEN_HS512_SECRET", "access_secret"); + + let token = Token::new(TokenType::AccessToken, "1").unwrap(); + let token_from_str = Token::from_str(TokenType::AccessToken, token.as_str()); + + let result = token_from_str.validate(); + + assert!(result.is_ok()); + } + + #[tokio::test] + async fn token_from_str_invalid_returns_err() { + env::set_var("ACCESS_TOKEN_HS512_SECRET", "access_secret"); + + let token = Token::from_str(TokenType::AccessToken, "invalid_token"); + let result = token.validate(); + + assert!(result.is_err()); + } +} diff --git a/src/api/ecdar_api.rs b/src/api/ecdar_api.rs index 6b1066e..8c78e38 100644 --- a/src/api/ecdar_api.rs +++ b/src/api/ecdar_api.rs @@ -17,7 +17,7 @@ impl ConcreteEcdarApi { /// The module uses the attribute macro `endpoints` to automatically implement the `endpoints` function as specified by the protobuffers. /// Therefore, if new endpoints or services are added and implemented by the api server, then the macro will automatically add it to the list. /// The macro can be found in the `ecdar_api_macros` crate. -#[ecdar_api_macros::endpoints] +#[ecdar_api::endpoints] mod routes { use super::super::server::protobuf::{ ecdar_api_auth_server::EcdarApiAuth, ecdar_api_server::EcdarApi, diff --git a/src/build.rs b/src/build.rs index 02170ec..7efe543 100644 --- a/src/build.rs +++ b/src/build.rs @@ -1,4 +1,3 @@ -#[allow(clippy::expect_used)] fn main() { tonic_build::configure() .type_attribute( @@ -109,6 +108,10 @@ fn main() { "SystemClock", "#[derive(serde::Serialize, serde::Deserialize)]", ) + .type_attribute( + "SyntaxFailure", + "#[derive(serde::Serialize, serde::Deserialize)]", + ) .enum_attribute("clock", "#[derive(serde::Serialize, serde::Deserialize)]") .enum_attribute( "node_type", diff --git a/src/contexts/context_impls/access_context.rs b/src/contexts/context_impls/access_context.rs index dae2817..2a45dc6 100644 --- a/src/contexts/context_impls/access_context.rs +++ b/src/contexts/context_impls/access_context.rs @@ -133,5 +133,399 @@ impl EntityContextTrait for AccessContext { } } #[cfg(test)] -#[path = "../../tests/contexts/access_context.rs"] -mod access_context_tests; +mod tests { + use super::super::super::helpers::{ + create_accesses, create_projects, create_users, get_reset_database_context, + }; + use crate::api::server::protobuf::AccessInfo; + use crate::contexts::context_traits::{AccessContextTrait, EntityContextTrait}; + use crate::{ + contexts::context_impls::AccessContext, + entities::{access, project, user}, + to_active_models, + }; + use sea_orm::{entity::prelude::*, IntoActiveModel}; + //use crate::contexts::helpers::get_reset_database_context; + + async fn seed_db() -> (AccessContext, access::Model, user::Model, project::Model) { + let db_context = get_reset_database_context().await; + + let access_context = AccessContext::new(db_context); + + let user = create_users(1)[0].clone(); + let project = create_projects(1, user.id)[0].clone(); + let access = create_accesses(1, user.id, project.id)[0].clone(); + + user::Entity::insert(user.clone().into_active_model()) + .exec(&access_context.db_context.get_connection()) + .await + .unwrap(); + project::Entity::insert(project.clone().into_active_model()) + .exec(&access_context.db_context.get_connection()) + .await + .unwrap(); + + (access_context, access, user, project) + } + + // Test the functionality of the 'create' function, which creates a access in the contexts + #[tokio::test] + async fn create_test() { + let (access_context, access, _, _) = seed_db().await; + + let created_access = access_context.create(access.clone()).await.unwrap(); + + let fetched_access = access::Entity::find_by_id(created_access.id) + .one(&access_context.db_context.get_connection()) + .await + .unwrap() + .unwrap(); + + // Assert if the fetched access is the same as the created access + assert_eq!(access, created_access); + assert_eq!(fetched_access, created_access); + } + + #[tokio::test] + async fn create_check_unique_pair_project_id_user_id_test() { + let (access_context, access, _, _) = seed_db().await; + + let _created_access_1 = access_context.create(access.clone()).await.unwrap(); + let _created_access_2 = access_context.create(access.clone()).await; + + assert!(matches!( + _created_access_2.unwrap_err().sql_err(), + Some(SqlErr::UniqueConstraintViolation(_)) + )); + } + + #[tokio::test] + async fn create_invalid_role_test() { + let (access_context, mut access, _, _) = seed_db().await; + + access.role = "abc".into(); + + let created_access = access_context.create(access.clone()).await; + + assert!(matches!( + created_access.unwrap_err().sql_err(), + Some(SqlErr::ForeignKeyConstraintViolation(_)) + )); + } + + #[tokio::test] + async fn create_auto_increment_test() { + let (access_context, _, user, project_1) = seed_db().await; + + let mut project_2 = create_projects(1, user.id)[0].clone(); + project_2.id = project_1.id + 1; + project_2.name = "project_2".to_string(); + + project::Entity::insert(project_2.into_active_model()) + .exec(&access_context.db_context.get_connection()) + .await + .unwrap(); + + let access_1 = access::Model { + id: 0, + role: "Editor".to_string(), + project_id: 1, + user_id: user.id, + }; + + let access_2 = access::Model { + id: 0, + role: "Editor".to_string(), + project_id: 2, + user_id: user.id, + }; + + let created_access1 = access_context.create(access_1.clone()).await.unwrap(); + let created_access2 = access_context.create(access_2.clone()).await.unwrap(); + + let fetched_access1 = access::Entity::find_by_id(created_access1.id) + .one(&access_context.db_context.get_connection()) + .await + .unwrap() + .unwrap(); + + let fetched_access2 = access::Entity::find_by_id(created_access2.id) + .one(&access_context.db_context.get_connection()) + .await + .unwrap() + .unwrap(); + + assert_ne!(fetched_access1.id, fetched_access2.id); + assert_ne!(created_access1.id, created_access2.id); + assert_eq!(created_access1.id, fetched_access1.id); + assert_eq!(created_access2.id, fetched_access2.id); + } + + #[tokio::test] + async fn get_by_id_test() { + let (access_context, access, _, _) = seed_db().await; + + access::Entity::insert(access.clone().into_active_model()) + .exec(&access_context.db_context.get_connection()) + .await + .unwrap(); + + // Fetches the access created using the 'get_by_id' function + let fetched_access = access_context.get_by_id(access.id).await.unwrap().unwrap(); + + // Assert if the fetched access is the same as the created access + assert_eq!(access, fetched_access); + } + + #[tokio::test] + async fn get_by_non_existing_id_test() { + let (access_context, _, _, _) = seed_db().await; + + let fetched_access = access_context.get_by_id(1).await.unwrap(); + + assert!(fetched_access.is_none()); + } + + #[tokio::test] + async fn get_all_test() { + let (access_context, _, user, project) = seed_db().await; + + // Creates a model of the access which will be created + let new_accesses = create_accesses(1, user.id, project.id); + + // Creates the access in the contexts using the 'create' function + access::Entity::insert_many(to_active_models!(new_accesses.clone())) + .exec(&access_context.db_context.get_connection()) + .await + .unwrap(); + + assert_eq!(access_context.get_all().await.unwrap().len(), 1); + + let mut sorted: Vec = new_accesses.clone(); + sorted.sort_by_key(|k| k.id); + + for (i, access) in sorted.into_iter().enumerate() { + assert_eq!(access, new_accesses[i]); + } + } + + #[tokio::test] + async fn get_all_empty_test() { + let (access_context, _, _, _) = seed_db().await; + + let result = access_context.get_all().await.unwrap(); + let empty_accesses: Vec = vec![]; + + assert_eq!(empty_accesses, result); + } + + #[tokio::test] + async fn update_test() { + let (access_context, access, _, _) = seed_db().await; + + access::Entity::insert(access.clone().into_active_model()) + .exec(&access_context.db_context.get_connection()) + .await + .unwrap(); + + let new_access = access::Model { ..access }; + + let updated_access = access_context.update(new_access.clone()).await.unwrap(); + + let fetched_access = access::Entity::find_by_id(updated_access.id) + .one(&access_context.db_context.get_connection()) + .await + .unwrap() + .unwrap(); + + assert_eq!(new_access, updated_access); + assert_eq!(updated_access, fetched_access); + } + + #[tokio::test] + async fn update_modifies_role_test() { + let (access_context, access, _, _) = seed_db().await; + + let access = access::Model { + role: "Editor".into(), + ..access + }; + + access::Entity::insert(access.clone().into_active_model()) + .exec(&access_context.db_context.get_connection()) + .await + .unwrap(); + + let new_access = access::Model { + role: "Commenter".into(), + ..access + }; + + let updated_access = access_context.update(new_access.clone()).await.unwrap(); + + assert_ne!(access, updated_access); + assert_ne!(access, new_access); + } + + #[tokio::test] + async fn update_does_not_modify_id_test() { + let (access_context, access, _, _) = seed_db().await; + access::Entity::insert(access.clone().into_active_model()) + .exec(&access_context.db_context.get_connection()) + .await + .unwrap(); + + let updated_access = access::Model { + id: &access.id + 1, + ..access.clone() + }; + let res = access_context.update(updated_access.clone()).await; + + assert!(matches!(res.unwrap_err(), DbErr::RecordNotUpdated)); + } + + #[tokio::test] + async fn update_does_not_modify_project_id_test() { + let (access_context, access, _, _) = seed_db().await; + + access::Entity::insert(access.clone().into_active_model()) + .exec(&access_context.db_context.get_connection()) + .await + .unwrap(); + + let updated_access = access::Model { + project_id: &access.project_id + 1, + ..access.clone() + }; + let res = access_context.update(updated_access.clone()).await.unwrap(); + + assert_eq!(access, res); + } + + #[tokio::test] + async fn update_does_not_modify_user_id_test() { + let (access_context, access, _, _) = seed_db().await; + + access::Entity::insert(access.clone().into_active_model()) + .exec(&access_context.db_context.get_connection()) + .await + .unwrap(); + + let updated_access = access::Model { + user_id: &access.user_id + 1, + ..access.clone() + }; + let res = access_context.update(updated_access.clone()).await.unwrap(); + + assert_eq!(access, res); + } + + #[tokio::test] + async fn update_invalid_role_test() { + let (access_context, mut access, _, _) = seed_db().await; + + access::Entity::insert(access.clone().into_active_model()) + .exec(&access_context.db_context.get_connection()) + .await + .unwrap(); + + access.role = "abc".into(); + + let updated_access = access_context.update(access.clone()).await; + + assert!(matches!( + updated_access.unwrap_err().sql_err(), + Some(SqlErr::ForeignKeyConstraintViolation(_)) + )); + } + + #[tokio::test] + async fn update_non_existing_id_test() { + let (access_context, access, _, _) = seed_db().await; + + let updated_access = access_context.update(access.clone()).await; + + assert!(matches!( + updated_access.unwrap_err(), + DbErr::RecordNotUpdated + )); + } + + #[tokio::test] + async fn delete_test() { + let (access_context, access, _, _) = seed_db().await; + + access::Entity::insert(access.clone().into_active_model()) + .exec(&access_context.db_context.get_connection()) + .await + .unwrap(); + + let deleted_access = access_context.delete(access.id).await.unwrap(); + + let all_accesses = access::Entity::find() + .all(&access_context.db_context.get_connection()) + .await + .unwrap(); + + assert_eq!(access, deleted_access); + assert!(all_accesses.is_empty()); + } + + #[tokio::test] + async fn delete_non_existing_id_test() { + let (access_context, _, _, _) = seed_db().await; + + let deleted_access = access_context.delete(1).await; + + assert!(matches!( + deleted_access.unwrap_err(), + DbErr::RecordNotFound(_) + )); + } + + #[tokio::test] + async fn get_by_uid_and_project_id_test() { + let (access_context, expected_access, user, project) = seed_db().await; + + access::Entity::insert(expected_access.clone().into_active_model()) + .exec(&access_context.db_context.get_connection()) + .await + .unwrap(); + + let access = access_context + .get_access_by_uid_and_project_id(user.id, project.id) + .await; + + assert_eq!(access.unwrap().unwrap(), expected_access); + } + + #[tokio::test] + async fn get_access_by_project_id_test_returns_ok() { + let (access_context, expected_access, _, model) = seed_db().await; + + let expected_access_access_info_vector = vec![AccessInfo { + id: expected_access.id, + project_id: expected_access.project_id, + user_id: expected_access.user_id, + role: expected_access.role.clone(), + }]; + + access::Entity::insert(expected_access.clone().into_active_model()) + .exec(&access_context.db_context.get_connection()) + .await + .unwrap(); + + let access = access_context.get_access_by_project_id(model.id).await; + + assert!(access.unwrap() == expected_access_access_info_vector); + } + + #[tokio::test] + async fn get_access_by_project_id_test_returns_empty() { + let (access_context, _, _, model) = seed_db().await; + + let access = access_context.get_access_by_project_id(model.id).await; + + assert!(access.unwrap().is_empty()); + } +} diff --git a/src/contexts/context_impls/in_use_context.rs b/src/contexts/context_impls/in_use_context.rs index 46745da..7c5ddbb 100644 --- a/src/contexts/context_impls/in_use_context.rs +++ b/src/contexts/context_impls/in_use_context.rs @@ -68,5 +68,276 @@ impl EntityContextTrait for InUseContext { } #[cfg(test)] -#[path = "../../tests/contexts/in_use_context.rs"] -mod in_use_context_tests; +mod tests { + use super::super::super::helpers::*; + use crate::{ + contexts::context_impls::InUseContext, + contexts::context_traits::EntityContextTrait, + entities::{in_use, project, session, user}, + to_active_models, + }; + use chrono::{Duration, Utc}; + use sea_orm::{entity::prelude::*, IntoActiveModel}; + use std::matches; + use std::ops::Add; + + async fn seed_db() -> ( + InUseContext, + in_use::Model, + session::Model, + project::Model, + user::Model, + ) { + let db_context = get_reset_database_context().await; + + let in_use_context = InUseContext::new(db_context); + + let user = create_users(1)[0].clone(); + let project = create_projects(1, user.id)[0].clone(); + let session = create_sessions(1, user.id)[0].clone(); + let in_use = create_in_uses(1, project.id, session.id)[0].clone(); + + user::Entity::insert(user.clone().into_active_model()) + .exec(&in_use_context.db_context.get_connection()) + .await + .unwrap(); + project::Entity::insert(project.clone().into_active_model()) + .exec(&in_use_context.db_context.get_connection()) + .await + .unwrap(); + session::Entity::insert(session.clone().into_active_model()) + .exec(&in_use_context.db_context.get_connection()) + .await + .unwrap(); + + (in_use_context, in_use, session, project, user) + } + + #[tokio::test] + async fn create_test() { + let (in_use_context, mut in_use, _, _, _) = seed_db().await; + + let inserted_in_use = in_use_context.create(in_use.clone()).await.unwrap(); + + in_use.latest_activity = inserted_in_use.latest_activity; + + let fetched_in_use = in_use::Entity::find_by_id(inserted_in_use.clone().project_id) + .one(&in_use_context.db_context.get_connection()) + .await + .unwrap() + .unwrap(); + + assert_eq!(in_use, inserted_in_use); + assert_eq!(in_use, fetched_in_use); + } + + #[tokio::test] + async fn create_default_latest_activity_test() { + let t_min = Utc::now().timestamp(); + + let (in_use_context, in_use, _, _, _) = seed_db().await; + + let inserted_in_use = in_use_context.create(in_use.clone()).await.unwrap(); + + let fetched_in_use = in_use::Entity::find_by_id(inserted_in_use.project_id) + .one(&in_use_context.db_context.get_connection()) + .await + .unwrap() + .unwrap(); + + let t_max = Utc::now().timestamp(); + + let t_actual = fetched_in_use.clone().latest_activity.timestamp(); + + assert!(t_min <= t_actual && t_actual <= t_max) + } + + #[tokio::test] + async fn get_by_id_test() { + let (in_use_context, in_use, _, _, _) = seed_db().await; + + in_use::Entity::insert(in_use.clone().into_active_model()) + .exec(&in_use_context.db_context.get_connection()) + .await + .unwrap(); + + let fetched_in_use = in_use_context + .get_by_id(in_use.project_id) + .await + .unwrap() + .unwrap(); + + assert_eq!(fetched_in_use, in_use) + } + + #[tokio::test] + async fn get_by_non_existing_id_test() { + let (in_use_context, _in_use, _, _, _) = seed_db().await; + + let in_use = in_use_context.get_by_id(1).await; + + assert!(in_use.unwrap().is_none()) + } + + #[tokio::test] + async fn get_all_test() { + let (in_use_context, _in_use, session, project, _user) = seed_db().await; + + let in_uses = create_in_uses(1, project.id, session.id); + + in_use::Entity::insert_many(to_active_models!(in_uses.clone())) + .exec(&in_use_context.db_context.get_connection()) + .await + .unwrap(); + + assert_eq!(in_use_context.get_all().await.unwrap().len(), 1); + } + + #[tokio::test] + async fn get_all_empty_test() { + let (in_use_context, _, _, _, _) = seed_db().await; + + let in_uses = in_use_context.get_all().await.unwrap(); + + assert_eq!(0, in_uses.len()) + } + + #[tokio::test] + async fn update_test() { + let (in_use_context, in_use, _, _, _) = seed_db().await; + + in_use::Entity::insert(in_use.clone().into_active_model()) + .exec(&in_use_context.db_context.get_connection()) + .await + .unwrap(); + + let new_in_use = in_use::Model { ..in_use }; + + let updated_in_use = in_use_context.update(new_in_use.clone()).await.unwrap(); + + let fetched_in_use = in_use::Entity::find_by_id(updated_in_use.project_id) + .one(&in_use_context.db_context.get_connection()) + .await + .unwrap() + .unwrap(); + + assert_eq!(new_in_use, updated_in_use); + assert_eq!(updated_in_use, fetched_in_use); + } + + #[tokio::test] + async fn update_modifies_latest_activity_test() { + let (in_use_context, in_use, _, _, _) = seed_db().await; + + in_use::Entity::insert(in_use.clone().into_active_model()) + .exec(&in_use_context.db_context.get_connection()) + .await + .unwrap(); + + let new_in_use = in_use::Model { + latest_activity: in_use.clone().latest_activity.add(Duration::seconds(1)), + ..in_use + }; + + let updated_in_use = in_use_context.update(new_in_use.clone()).await.unwrap(); + + assert_ne!(in_use, updated_in_use); + assert_ne!(in_use, new_in_use); + } + + #[tokio::test] + async fn update_modifies_session_id_test() { + let (in_use_context, in_use, _, _, _) = seed_db().await; + + in_use::Entity::insert(in_use.clone().into_active_model()) + .exec(&in_use_context.db_context.get_connection()) + .await + .unwrap(); + + let mut session2 = create_sessions(1, in_use.session_id)[0].clone(); + session2.id = in_use.session_id + 1; + session2.refresh_token = "new_refresh_token".to_string(); + session2.access_token = "new_access_token".to_string(); + + session::Entity::insert(session2.clone().into_active_model()) + .exec(&in_use_context.db_context.get_connection()) + .await + .unwrap(); + + let new_in_use = in_use::Model { + session_id: in_use.session_id + 1, + ..in_use + }; + + let updated_in_use = in_use_context.update(new_in_use.clone()).await.unwrap(); + + assert_ne!(in_use, updated_in_use); + assert_ne!(in_use, new_in_use); + } + + #[tokio::test] + async fn update_does_not_modify_project_id_test() { + let (in_use_context, in_use, _, _, _) = seed_db().await; + + in_use::Entity::insert(in_use.clone().into_active_model()) + .exec(&in_use_context.db_context.get_connection()) + .await + .unwrap(); + + let updated_in_use = in_use::Model { + project_id: in_use.project_id + 1, + ..in_use.clone() + }; + + let updated_in_use = in_use_context.update(updated_in_use.clone()).await; + + assert!(matches!( + updated_in_use.unwrap_err(), + DbErr::RecordNotUpdated + )); + } + + #[tokio::test] + async fn update_non_existing_id_test() { + let (in_use_context, in_use, _, _, _) = seed_db().await; + + let updated_in_use = in_use_context.update(in_use.clone()).await; + + assert!(matches!( + updated_in_use.unwrap_err(), + DbErr::RecordNotUpdated + )); + } + + #[tokio::test] + async fn delete_test() { + let (in_use_context, in_use, _, _, _) = seed_db().await; + + in_use::Entity::insert(in_use.clone().into_active_model()) + .exec(&in_use_context.db_context.get_connection()) + .await + .unwrap(); + + let deleted_in_use = in_use_context.delete(in_use.project_id).await.unwrap(); + + let all_in_uses = in_use::Entity::find() + .all(&in_use_context.db_context.get_connection()) + .await + .unwrap(); + + assert_eq!(in_use, deleted_in_use); + assert!(all_in_uses.is_empty()); + } + + #[tokio::test] + async fn delete_non_existing_id_test() { + let (in_use_context, _, _, _, _) = seed_db().await; + + let deleted_in_use = in_use_context.delete(1).await; + + assert!(matches!( + deleted_in_use.unwrap_err(), + DbErr::RecordNotFound(_) + )) + } +} diff --git a/src/contexts/context_impls/project_context.rs b/src/contexts/context_impls/project_context.rs index fa64d97..eb6019e 100644 --- a/src/contexts/context_impls/project_context.rs +++ b/src/contexts/context_impls/project_context.rs @@ -150,5 +150,414 @@ impl EntityContextTrait for ProjectContext { } #[cfg(test)] -#[path = "../../tests/contexts/project_context.rs"] -mod project_context_tests; +mod tests { + use super::super::super::helpers::*; + use crate::{ + contexts::context_impls::ProjectContext, + contexts::context_traits::EntityContextTrait, + entities::{access, in_use, project, query, session, user}, + to_active_models, + }; + use sea_orm::error::DbErr; + use sea_orm::{entity::prelude::*, IntoActiveModel}; + use std::matches; + + async fn seed_db() -> (ProjectContext, project::Model, user::Model) { + let db_context = get_reset_database_context().await; + + let project_context = ProjectContext::new(db_context); + + let user = create_users(1)[0].clone(); + let project = create_projects(1, user.id)[0].clone(); + + user::Entity::insert(user.clone().into_active_model()) + .exec(&project_context.db_context.get_connection()) + .await + .unwrap(); + + (project_context, project, user) + } + + #[tokio::test] + async fn create_test() { + let (project_context, project, _) = seed_db().await; + + let created_project = project_context.create(project.clone()).await.unwrap(); + + let fetched_project = project::Entity::find_by_id(created_project.id) + .one(&project_context.db_context.get_connection()) + .await + .unwrap() + .unwrap(); + + assert_eq!(project, created_project); + assert_eq!(fetched_project, created_project); + } + + #[tokio::test] + async fn create_auto_increment_test() { + let (project_context, project, _) = seed_db().await; + + let projects = create_projects(2, project.owner_id); + + let created_project1 = project_context.create(projects[0].clone()).await.unwrap(); + let created_project2 = project_context.create(projects[1].clone()).await.unwrap(); + + let fetched_project1 = project::Entity::find_by_id(created_project1.id) + .one(&project_context.db_context.get_connection()) + .await + .unwrap() + .unwrap(); + + let fetched_project2 = project::Entity::find_by_id(created_project2.id) + .one(&project_context.db_context.get_connection()) + .await + .unwrap() + .unwrap(); + + assert_ne!(fetched_project1.id, fetched_project2.id); + assert_ne!(created_project1.id, created_project2.id); + assert_eq!(created_project1.id, fetched_project1.id); + assert_eq!(created_project2.id, fetched_project2.id); + } + + #[tokio::test] + async fn get_by_id_test() { + let (project_context, project, _) = seed_db().await; + + project::Entity::insert(project.clone().into_active_model()) + .exec(&project_context.db_context.get_connection()) + .await + .unwrap(); + + let fetched_project = project_context + .get_by_id(project.id) + .await + .unwrap() + .unwrap(); + + assert_eq!(project, fetched_project); + } + + #[tokio::test] + async fn get_by_non_existing_id_test() { + let (project_context, _, _) = seed_db().await; + + let fetched_project = project_context.get_by_id(1).await.unwrap(); + + assert!(fetched_project.is_none()); + } + + #[tokio::test] + async fn get_all_test() { + let (project_context, _, user) = seed_db().await; + + let new_projects = create_projects(3, user.id); + + project::Entity::insert_many(to_active_models!(new_projects.clone())) + .exec(&project_context.db_context.get_connection()) + .await + .unwrap(); + + assert_eq!(project_context.get_all().await.unwrap().len(), 3); + + let mut sorted = new_projects.clone(); + sorted.sort_by_key(|k| k.id); + + for (i, project) in sorted.into_iter().enumerate() { + assert_eq!(project, new_projects[i]); + } + } + + #[tokio::test] + async fn get_all_empty_test() { + let (project_context, _, _) = seed_db().await; + + let result = project_context.get_all().await.unwrap(); + let empty_projects: Vec = vec![]; + + assert_eq!(empty_projects, result); + } + + #[tokio::test] + async fn update_test() { + let (project_context, project, _) = seed_db().await; + + project::Entity::insert(project.clone().into_active_model()) + .exec(&project_context.db_context.get_connection()) + .await + .unwrap(); + + let new_project = project::Model { ..project }; + + let updated_project = project_context.update(new_project.clone()).await.unwrap(); + + let fetched_project = project::Entity::find_by_id(updated_project.id) + .one(&project_context.db_context.get_connection()) + .await + .unwrap() + .unwrap(); + + assert_eq!(new_project, updated_project); + assert_eq!(updated_project, fetched_project); + } + + #[tokio::test] + async fn update_modifies_name_test() { + let (project_context, project, _) = seed_db().await; + + let project = project::Model { + name: "project1".into(), + ..project.clone() + }; + + project::Entity::insert(project.clone().into_active_model()) + .exec(&project_context.db_context.get_connection()) + .await + .unwrap(); + + let new_project = project::Model { + name: "project2".into(), + ..project.clone() + }; + + let updated_project = project_context.update(new_project.clone()).await.unwrap(); + + assert_ne!(project, updated_project); + assert_ne!(project, new_project); + } + + #[tokio::test] + async fn update_modifies_components_info_test() { + let (project_context, project, _) = seed_db().await; + + let project = project::Model { + components_info: "{\"a\":1}".to_owned().parse().unwrap(), + ..project.clone() + }; + + project::Entity::insert(project.clone().into_active_model()) + .exec(&project_context.db_context.get_connection()) + .await + .unwrap(); + + let new_project = project::Model { + components_info: "{\"a\":2}".to_owned().parse().unwrap(), + ..project.clone() + }; + + let updated_project = project_context.update(new_project.clone()).await.unwrap(); + + assert_ne!(project, updated_project); + assert_ne!(project, new_project); + } + + #[tokio::test] + async fn update_does_not_modify_id_test() { + let (project_context, project, _) = seed_db().await; + + project::Entity::insert(project.clone().into_active_model()) + .exec(&project_context.db_context.get_connection()) + .await + .unwrap(); + + let new_project = project::Model { + id: &project.id + 1, + ..project.clone() + }; + + let res = project_context.update(new_project.clone()).await; + + assert!(matches!(res.unwrap_err(), DbErr::RecordNotUpdated)); + } + + #[tokio::test] + async fn update_does_not_modify_owner_id_test() { + let (project_context, project, _) = seed_db().await; + + project::Entity::insert(project.clone().into_active_model()) + .exec(&project_context.db_context.get_connection()) + .await + .unwrap(); + + let new_project = project::Model { + owner_id: &project.owner_id + 1, + ..project.clone() + }; + + let res = project_context.update(new_project.clone()).await.unwrap(); + + assert_eq!(project, res); + } + + #[tokio::test] + async fn update_check_query_outdated_test() { + let (project_context, project, _) = seed_db().await; + + let mut query = create_queries(1, project.id)[0].clone(); + + query.outdated = false; + + project::Entity::insert(project.clone().into_active_model()) + .exec(&project_context.db_context.get_connection()) + .await + .unwrap(); + + query::Entity::insert(query.clone().into_active_model()) + .exec(&project_context.db_context.get_connection()) + .await + .unwrap(); + + let new_project = project::Model { ..project }; + + let updated_project = project_context.update(new_project.clone()).await.unwrap(); + + let fetched_query = query::Entity::find_by_id(updated_project.id) + .one(&project_context.db_context.get_connection()) + .await + .unwrap() + .unwrap(); + + assert!(fetched_query.outdated); + } + + #[tokio::test] + async fn update_non_existing_id_test() { + let (project_context, project, _) = seed_db().await; + + let updated_project = project_context.update(project.clone()).await; + + assert!(matches!( + updated_project.unwrap_err(), + DbErr::RecordNotUpdated + )); + } + + #[tokio::test] + async fn delete_test() { + // Setting up contexts and user context + let (project_context, project, _) = seed_db().await; + + project::Entity::insert(project.clone().into_active_model()) + .exec(&project_context.db_context.get_connection()) + .await + .unwrap(); + + let deleted_project = project_context.delete(project.id).await.unwrap(); + + let all_projects = project::Entity::find() + .all(&project_context.db_context.get_connection()) + .await + .unwrap(); + + assert_eq!(project, deleted_project); + assert_eq!(all_projects.len(), 0); + } + + #[tokio::test] + async fn delete_cascade_query_test() { + let (project_context, project, _) = seed_db().await; + + let query = create_queries(1, project.clone().id)[0].clone(); + + project::Entity::insert(project.clone().into_active_model()) + .exec(&project_context.db_context.get_connection()) + .await + .unwrap(); + query::Entity::insert(query.clone().into_active_model()) + .exec(&project_context.db_context.get_connection()) + .await + .unwrap(); + + project_context.delete(project.id).await.unwrap(); + + let all_queries = query::Entity::find() + .all(&project_context.db_context.get_connection()) + .await + .unwrap(); + let all_projects = project::Entity::find() + .all(&project_context.db_context.get_connection()) + .await + .unwrap(); + + assert_eq!(all_queries.len(), 0); + assert_eq!(all_projects.len(), 0); + } + + #[tokio::test] + async fn delete_cascade_access_test() { + let (project_context, project, _) = seed_db().await; + + let access = create_accesses(1, 1, project.clone().id)[0].clone(); + + project::Entity::insert(project.clone().into_active_model()) + .exec(&project_context.db_context.get_connection()) + .await + .unwrap(); + access::Entity::insert(access.clone().into_active_model()) + .exec(&project_context.db_context.get_connection()) + .await + .unwrap(); + + project_context.delete(project.id).await.unwrap(); + + let all_projects = project::Entity::find() + .all(&project_context.db_context.get_connection()) + .await + .unwrap(); + let all_accesses = access::Entity::find() + .all(&project_context.db_context.get_connection()) + .await + .unwrap(); + + assert_eq!(all_projects.len(), 0); + assert_eq!(all_accesses.len(), 0); + } + + #[tokio::test] + async fn delete_cascade_in_use_test() { + let (project_context, project, user) = seed_db().await; + + let session = create_sessions(1, user.clone().id)[0].clone(); + let in_use = create_in_uses(1, project.clone().id, 1)[0].clone(); + + session::Entity::insert(session.clone().into_active_model()) + .exec(&project_context.db_context.get_connection()) + .await + .unwrap(); + project::Entity::insert(project.clone().into_active_model()) + .exec(&project_context.db_context.get_connection()) + .await + .unwrap(); + in_use::Entity::insert(in_use.clone().into_active_model()) + .exec(&project_context.db_context.get_connection()) + .await + .unwrap(); + + project_context.delete(project.id).await.unwrap(); + + let all_projects = project::Entity::find() + .all(&project_context.db_context.get_connection()) + .await + .unwrap(); + let all_in_uses = in_use::Entity::find() + .all(&project_context.db_context.get_connection()) + .await + .unwrap(); + + assert_eq!(all_projects.len(), 0); + assert_eq!(all_in_uses.len(), 0); + } + + #[tokio::test] + async fn delete_non_existing_id_test() { + let (project_context, _, _) = seed_db().await; + + let deleted_project = project_context.delete(1).await; + + assert!(matches!( + deleted_project.unwrap_err(), + DbErr::RecordNotFound(_) + )); + } +} diff --git a/src/contexts/context_impls/query_context.rs b/src/contexts/context_impls/query_context.rs index 4656871..bfd662b 100644 --- a/src/contexts/context_impls/query_context.rs +++ b/src/contexts/context_impls/query_context.rs @@ -128,5 +128,320 @@ impl EntityContextTrait for QueryContext { } #[cfg(test)] -#[path = "../../tests/contexts/query_context.rs"] -mod query_context_tests; +mod tests { + use super::super::super::helpers::{ + create_projects, create_queries, create_users, get_reset_database_context, + }; + use crate::{ + contexts::context_impls::QueryContext, + contexts::context_traits::EntityContextTrait, + entities::{project, query, user}, + to_active_models, + }; + use sea_orm::{entity::prelude::*, IntoActiveModel}; + + async fn seed_db() -> (QueryContext, query::Model, project::Model) { + let db_context = get_reset_database_context().await; + + let query_context = QueryContext::new(db_context); + + let user = create_users(1)[0].clone(); + let project = create_projects(1, user.id)[0].clone(); + let query = create_queries(1, project.id)[0].clone(); + + user::Entity::insert(user.clone().into_active_model()) + .exec(&query_context.db_context.get_connection()) + .await + .unwrap(); + project::Entity::insert(project.clone().into_active_model()) + .exec(&query_context.db_context.get_connection()) + .await + .unwrap(); + + (query_context, query, project) + } + + #[tokio::test] + async fn create_test() { + let (query_context, query, _) = seed_db().await; + + let created_query = query_context.create(query.clone()).await.unwrap(); + + let fetched_query = query::Entity::find_by_id(created_query.id) + .one(&query_context.db_context.get_connection()) + .await + .unwrap() + .unwrap(); + + // Assert if the fetched access is the same as the created access + assert_eq!(query, created_query); + assert_eq!(fetched_query, created_query); + } + + #[tokio::test] + async fn create_default_outdated_test() { + let (query_context, query, _) = seed_db().await; + + let _inserted_query = query_context.create(query.clone()).await.unwrap(); + + let fetched_query = query::Entity::find_by_id(query.project_id) + .one(&query_context.db_context.get_connection()) + .await + .unwrap() + .unwrap(); + + assert!(fetched_query.outdated) + } + + #[tokio::test] + async fn create_auto_increment_test() { + let (query_context, query, _) = seed_db().await; + + let created_query1 = query_context.create(query.clone()).await.unwrap(); + let created_query2 = query_context.create(query.clone()).await.unwrap(); + + let fetched_query1 = query::Entity::find_by_id(created_query1.id) + .one(&query_context.db_context.get_connection()) + .await + .unwrap() + .unwrap(); + + let fetched_query2 = query::Entity::find_by_id(created_query2.id) + .one(&query_context.db_context.get_connection()) + .await + .unwrap() + .unwrap(); + + assert_ne!(fetched_query1.id, fetched_query2.id); + assert_ne!(created_query1.id, created_query2.id); + assert_eq!(created_query1.id, fetched_query1.id); + assert_eq!(created_query2.id, fetched_query2.id); + } + + #[tokio::test] + async fn get_by_id_test() { + let (query_context, query, _) = seed_db().await; + + query::Entity::insert(query.clone().into_active_model()) + .exec(&query_context.db_context.get_connection()) + .await + .unwrap(); + + let fetched_in_use = query_context + .get_by_id(query.project_id) + .await + .unwrap() + .unwrap(); + + assert_eq!(fetched_in_use, query) + } + + #[tokio::test] + async fn get_by_non_existing_id_test() { + let (query_context, _, _) = seed_db().await; + + let query = query_context.get_by_id(1).await; + + assert!(query.unwrap().is_none()) + } + + #[tokio::test] + async fn get_all_test() { + let (query_context, _, project) = seed_db().await; + + let queries = create_queries(10, project.id); + + query::Entity::insert_many(to_active_models!(queries.clone())) + .exec(&query_context.db_context.get_connection()) + .await + .unwrap(); + + assert_eq!(query_context.get_all().await.unwrap().len(), 10); + + let mut sorted = queries.clone(); + sorted.sort_by_key(|k| k.project_id); + + for (i, query) in sorted.into_iter().enumerate() { + assert_eq!(query, queries[i]); + } + } + + #[tokio::test] + async fn get_all_empty_test() { + let (query_context, _, _) = seed_db().await; + + let queries = query_context.get_all().await.unwrap(); + + assert_eq!(0, queries.len()) + } + + #[tokio::test] + async fn update_test() { + let (query_context, query, _) = seed_db().await; + + query::Entity::insert(query.clone().into_active_model()) + .exec(&query_context.db_context.get_connection()) + .await + .unwrap(); + + let new_query = query::Model { ..query }; + + let updated_query = query_context.update(new_query.clone()).await.unwrap(); + + let fetched_query = query::Entity::find_by_id(updated_query.project_id) + .one(&query_context.db_context.get_connection()) + .await + .unwrap() + .unwrap(); + + assert_eq!(new_query, updated_query); + assert_eq!(updated_query, fetched_query); + } + + #[tokio::test] + async fn update_modifies_string_test() { + let (query_context, query, _) = seed_db().await; + + query::Entity::insert(query.clone().into_active_model()) + .exec(&query_context.db_context.get_connection()) + .await + .unwrap(); + + let new_query = query::Model { + string: query.clone().string + "123", + ..query.clone() + }; + + let updated_query = query_context.update(new_query.clone()).await.unwrap(); + + assert_ne!(query, updated_query); + assert_ne!(query, new_query); + } + + #[tokio::test] + async fn update_modifies_outdated_test() { + let (query_context, query, _) = seed_db().await; + + query::Entity::insert(query.clone().into_active_model()) + .exec(&query_context.db_context.get_connection()) + .await + .unwrap(); + + let new_query = query::Model { + outdated: !query.clone().outdated, + ..query.clone() + }; + + let updated_query = query_context.update(new_query.clone()).await.unwrap(); + + assert_ne!(query, updated_query); + assert_ne!(query, new_query); + } + + #[tokio::test] + async fn update_modifies_result_test() { + let (query_context, mut query, _) = seed_db().await; + + query.result = Some("{}".to_owned().parse().unwrap()); + + query::Entity::insert(query.clone().into_active_model()) + .exec(&query_context.db_context.get_connection()) + .await + .unwrap(); + + let new_query = query::Model { + result: None, + ..query.clone() + }; + + let updated_query = query_context.update(new_query.clone()).await.unwrap(); + + assert_ne!(query, updated_query); + assert_ne!(query, new_query); + } + + #[tokio::test] + async fn update_does_not_modify_id_test() { + let (query_context, query, _) = seed_db().await; + + query::Entity::insert(query.clone().into_active_model()) + .exec(&query_context.db_context.get_connection()) + .await + .unwrap(); + + let new_query = query::Model { + id: query.id + 1, + ..query.clone() + }; + + let updated_query = query_context.update(new_query.clone()).await; + + assert!(matches!( + updated_query.unwrap_err(), + DbErr::RecordNotUpdated + )); + } + + #[tokio::test] + async fn update_does_not_modify_project_id_test() { + let (query_context, query, _) = seed_db().await; + + query::Entity::insert(query.clone().into_active_model()) + .exec(&query_context.db_context.get_connection()) + .await + .unwrap(); + + let new_query = query::Model { + project_id: query.project_id + 1, + ..query.clone() + }; + + let updated_query = query_context.update(new_query.clone()).await.unwrap(); + + assert_eq!(query, updated_query); + } + + #[tokio::test] + async fn update_non_existing_id_test() { + let (query_context, query, _) = seed_db().await; + + let updated_query = query_context.update(query.clone()).await; + + assert!(matches!( + updated_query.unwrap_err(), + DbErr::RecordNotUpdated + )); + } + + #[tokio::test] + async fn delete_test() { + let (query_context, query, _) = seed_db().await; + + query::Entity::insert(query.clone().into_active_model()) + .exec(&query_context.db_context.get_connection()) + .await + .unwrap(); + + let deleted_query = query_context.delete(query.project_id).await.unwrap(); + + let all_queries = query::Entity::find() + .all(&query_context.db_context.get_connection()) + .await + .unwrap(); + + assert_eq!(query, deleted_query); + assert!(all_queries.is_empty()); + } + + #[tokio::test] + async fn delete_non_existing_id_test() { + let (query_context, _, _) = seed_db().await; + + let deleted_query = query_context.delete(1).await; + + assert!(matches!( + deleted_query.unwrap_err(), + DbErr::RecordNotFound(_) + )) + } +} diff --git a/src/contexts/context_impls/session_context.rs b/src/contexts/context_impls/session_context.rs index 874431a..6238722 100644 --- a/src/contexts/context_impls/session_context.rs +++ b/src/contexts/context_impls/session_context.rs @@ -178,5 +178,428 @@ impl EntityContextTrait for SessionContext { } #[cfg(test)] -#[path = "../../tests/contexts/session_context.rs"] -mod session_context_tests; +mod tests { + use super::super::super::helpers::*; + use crate::api::auth::TokenType; + use sea_orm::{entity::prelude::*, IntoActiveModel}; + use std::ops::Add; + + use crate::{ + contexts::context_impls::SessionContext, + contexts::context_traits::{EntityContextTrait, SessionContextTrait}, + entities::{in_use, project, session, user}, + to_active_models, + }; + + use chrono::{Duration, Utc}; + + async fn seed_db() -> (SessionContext, session::Model, user::Model, project::Model) { + let db_context = get_reset_database_context().await; + + let session_context = SessionContext::new(db_context); + + let user = create_users(1)[0].clone(); + let project = create_projects(1, user.id)[0].clone(); + let session = create_sessions(1, user.id)[0].clone(); + + user::Entity::insert(user.clone().into_active_model()) + .exec(&session_context.db_context.get_connection()) + .await + .unwrap(); + project::Entity::insert(project.clone().into_active_model()) + .exec(&session_context.db_context.get_connection()) + .await + .unwrap(); + + (session_context, session, user, project) + } + + #[tokio::test] + async fn create_test() { + // Setting up a sqlite contexts in memory. + let (session_context, mut session, _, _) = seed_db().await; + + let created_session = session_context.create(session.clone()).await.unwrap(); + + session.updated_at = created_session.updated_at; + + let fetched_session = session::Entity::find_by_id(created_session.id) + .one(&session_context.db_context.get_connection()) + .await + .unwrap() + .unwrap(); + + assert_eq!(session, created_session); + assert_eq!(fetched_session, created_session); + } + + #[tokio::test] + async fn create_default_created_at_test() { + let t_min = Utc::now().timestamp(); + + let (session_context, session, _, _) = seed_db().await; + + let _created_session = session_context.create(session.clone()).await.unwrap(); + + let fetched_session = session::Entity::find_by_id(1) + .one(&session_context.db_context.get_connection()) + .await + .unwrap() + .unwrap(); + + let t_max = Utc::now().timestamp(); + let t_actual = fetched_session.clone().updated_at.timestamp(); + + assert!(t_min <= t_actual && t_actual <= t_max) + } + + #[tokio::test] + async fn create_auto_increment_test() { + // Setting up contexts and session context + let (session_context, _, user, _) = seed_db().await; + + let sessions = create_sessions(2, user.id); + + // Creates the sessions in the contexts using the 'create' function + let created_session1 = session_context.create(sessions[0].clone()).await.unwrap(); + let created_session2 = session_context.create(sessions[1].clone()).await.unwrap(); + + let fetched_session1 = session::Entity::find_by_id(created_session1.id) + .one(&session_context.db_context.get_connection()) + .await + .unwrap() + .unwrap(); + + let fetched_session2 = session::Entity::find_by_id(created_session2.id) + .one(&session_context.db_context.get_connection()) + .await + .unwrap() + .unwrap(); + + // Assert if the new_session, created_session, and fetched_session are the same + assert_ne!(fetched_session1.id, fetched_session2.id); + assert_ne!(created_session1.id, created_session2.id); + assert_eq!(created_session1.id, fetched_session1.id); + assert_eq!(created_session2.id, fetched_session2.id); + } + + #[tokio::test] + async fn create_non_unique_refresh_token_test() { + let (session_context, _, _, user) = seed_db().await; + + let mut sessions = create_sessions(2, user.id); + + sessions[1].refresh_token = sessions[0].refresh_token.clone(); + + let _created_session1 = session_context.create(sessions[0].clone()).await.unwrap(); + let created_session2 = session_context.create(sessions[1].clone()).await; + + assert!(matches!( + created_session2.unwrap_err().sql_err(), + Some(SqlErr::UniqueConstraintViolation(_)) + )); + } + + #[tokio::test] + async fn get_by_id_test() { + let (session_context, session, _, _) = seed_db().await; + + session::Entity::insert(session.clone().into_active_model()) + .exec(&session_context.db_context.get_connection()) + .await + .unwrap(); + + let fetched_session = session_context + .get_by_id(session.id) + .await + .unwrap() + .unwrap(); + + assert_eq!(session, fetched_session); + } + + #[tokio::test] + async fn get_by_non_existing_id_test() { + let (session_context, _, _, _) = seed_db().await; + + let fetched_session = session_context.get_by_id(1).await.unwrap(); + + assert!(fetched_session.is_none()); + } + + #[tokio::test] + async fn get_all_test() { + let (session_context, _, user, _) = seed_db().await; + + let new_sessions = create_sessions(3, user.id); + + session::Entity::insert_many(to_active_models!(new_sessions.clone())) + .exec(&session_context.db_context.get_connection()) + .await + .unwrap(); + + assert_eq!(session_context.get_all().await.unwrap().len(), 3); + + let mut sorted: Vec = new_sessions.clone(); + sorted.sort_by_key(|k| k.id); + + for (i, session) in sorted.into_iter().enumerate() { + assert_eq!(session, new_sessions[i]); + } + } + + #[tokio::test] + async fn get_all_empty_test() { + let (session_context, _, _, _) = seed_db().await; + + let result = session_context.get_all().await.unwrap(); + let empty_accesses: Vec = vec![]; + + assert_eq!(empty_accesses, result); + } + + #[tokio::test] + async fn update_test() { + let (session_context, session, _, _) = seed_db().await; + + session::Entity::insert(session.clone().into_active_model()) + .exec(&session_context.db_context.get_connection()) + .await + .unwrap(); + + //A session has nothing to update + let mut new_session = session::Model { ..session }; + + let mut updated_session = session_context.update(new_session.clone()).await.unwrap(); + + let fetched_session = session::Entity::find_by_id(updated_session.id) + .one(&session_context.db_context.get_connection()) + .await + .unwrap() + .unwrap(); + + new_session.updated_at = fetched_session.updated_at; + updated_session.updated_at = fetched_session.updated_at; + + assert_eq!(new_session, updated_session); + assert_eq!(updated_session, fetched_session); + } + + #[tokio::test] + async fn update_does_not_modify_id_test() { + let (session_context, session, _, _) = seed_db().await; + session::Entity::insert(session.clone().into_active_model()) + .exec(&session_context.db_context.get_connection()) + .await + .unwrap(); + + let updated_session = session::Model { + id: &session.id + 1, + ..session.clone() + }; + let res = session_context.update(updated_session.clone()).await; + + assert!(matches!(res.unwrap_err(), DbErr::RecordNotUpdated)); + } + + #[tokio::test] + async fn update_does_modifies_updated_at_automatically_test() { + let (session_context, mut session, _, _) = seed_db().await; + session::Entity::insert(session.clone().into_active_model()) + .exec(&session_context.db_context.get_connection()) + .await + .unwrap(); + + let updated_session = session::Model { + updated_at: session.clone().updated_at.add(Duration::seconds(1)), + ..session.clone() + }; + let res = session_context + .update(updated_session.clone()) + .await + .unwrap(); + + assert!(session.updated_at < res.updated_at); + + session.updated_at = res.updated_at; + + assert_eq!(session, res); + } + + #[tokio::test] + async fn update_does_not_modify_user_id_test() { + let (session_context, mut session, _, _) = seed_db().await; + session::Entity::insert(session.clone().into_active_model()) + .exec(&session_context.db_context.get_connection()) + .await + .unwrap(); + + let updated_session = session::Model { + user_id: &session.user_id + 1, + ..session.clone() + }; + let res = session_context + .update(updated_session.clone()) + .await + .unwrap(); + + session.updated_at = res.updated_at; + + assert_eq!(session, res); + } + + #[tokio::test] + async fn update_non_existing_id_test() { + let (session_context, session, _, _) = seed_db().await; + + let updated_session = session_context.update(session.clone()).await; + + assert!(matches!( + updated_session.unwrap_err(), + DbErr::RecordNotUpdated + )); + } + + #[tokio::test] + async fn delete_test() { + let (session_context, session, _, _) = seed_db().await; + + session::Entity::insert(session.clone().into_active_model()) + .exec(&session_context.db_context.get_connection()) + .await + .unwrap(); + + let deleted_session = session_context.delete(session.id).await.unwrap(); + + let all_sessions = session::Entity::find() + .all(&session_context.db_context.get_connection()) + .await + .unwrap(); + + assert_eq!(session, deleted_session); + assert!(all_sessions.is_empty()); + } + + #[tokio::test] + async fn delete_cascade_in_use_test() { + let (session_context, session, _, project) = seed_db().await; + + let in_use = create_in_uses(1, project.id, session.id)[0].clone(); + + session::Entity::insert(session.clone().into_active_model()) + .exec(&session_context.db_context.get_connection()) + .await + .unwrap(); + in_use::Entity::insert(in_use.clone().into_active_model()) + .exec(&session_context.db_context.get_connection()) + .await + .unwrap(); + + session_context.delete(session.id).await.unwrap(); + + let all_sessions = session::Entity::find() + .all(&session_context.db_context.get_connection()) + .await + .unwrap(); + let all_in_uses = in_use::Entity::find() + .all(&session_context.db_context.get_connection()) + .await + .unwrap(); + + assert_eq!(all_sessions.len(), 0); + assert_eq!(all_in_uses.len(), 0); + } + + #[tokio::test] + async fn delete_non_existing_id_test() { + let (session_context, _, _, _) = seed_db().await; + + let deleted_session = session_context.delete(1).await; + + assert!(matches!( + deleted_session.unwrap_err(), + DbErr::RecordNotFound(_) + )); + } + + #[tokio::test] + async fn get_by_token_refresh_test() { + let (session_context, session, _, _) = seed_db().await; + + session::Entity::insert(session.clone().into_active_model()) + .exec(&session_context.db_context.get_connection()) + .await + .unwrap(); + + let fetched_session = session_context + .get_by_token(TokenType::RefreshToken, session.refresh_token.clone()) + .await + .unwrap(); + + assert_eq!( + fetched_session.unwrap().refresh_token, + session.refresh_token + ); + } + + #[tokio::test] + async fn get_by_token_access_test() { + let (session_context, session, _, _) = seed_db().await; + + session::Entity::insert(session.clone().into_active_model()) + .exec(&session_context.db_context.get_connection()) + .await + .unwrap(); + + let fetched_session = session_context + .get_by_token(TokenType::AccessToken, session.access_token.clone()) + .await + .unwrap(); + + assert_eq!(fetched_session.unwrap().access_token, session.access_token); + } + + #[tokio::test] + async fn delete_by_token_refresh_test() { + let (session_context, session, _, _) = seed_db().await; + + session::Entity::insert(session.clone().into_active_model()) + .exec(&session_context.db_context.get_connection()) + .await + .unwrap(); + + session_context + .delete_by_token(TokenType::RefreshToken, session.refresh_token.clone()) + .await + .unwrap(); + + let fetched_session = session_context + .get_by_token(TokenType::RefreshToken, session.refresh_token.clone()) + .await + .unwrap(); + + assert!(fetched_session.is_none()); + } + + #[tokio::test] + async fn delete_by_token_access_test() { + let (session_context, session, _, _) = seed_db().await; + + session::Entity::insert(session.clone().into_active_model()) + .exec(&session_context.db_context.get_connection()) + .await + .unwrap(); + + session_context + .delete_by_token(TokenType::AccessToken, session.access_token.clone()) + .await + .unwrap(); + + let fetched_session = session_context + .get_by_token(TokenType::AccessToken, session.access_token.clone()) + .await + .unwrap(); + + assert!(fetched_session.is_none()); + } +} diff --git a/src/contexts/context_impls/user_context.rs b/src/contexts/context_impls/user_context.rs index 8f0ee8e..9465c22 100644 --- a/src/contexts/context_impls/user_context.rs +++ b/src/contexts/context_impls/user_context.rs @@ -147,5 +147,536 @@ impl EntityContextTrait for UserContext { } #[cfg(test)] -#[path = "../../tests/contexts/user_context.rs"] -mod user_context_tests; +mod tests { + use crate::contexts::helpers::*; + use crate::{ + contexts::context_impls::UserContext, + contexts::context_traits::{EntityContextTrait, UserContextTrait}, + entities::{access, project, session, user}, + to_active_models, + }; + use sea_orm::{entity::prelude::*, IntoActiveModel}; + use std::matches; + + async fn seed_db() -> (UserContext, user::Model) { + let db_context = get_reset_database_context().await; + + let user_context = UserContext::new(db_context); + + let user = create_users(1)[0].clone(); + + (user_context, user) + } + + // Test the functionality of the 'create' function, which creates a user in the contexts + #[tokio::test] + async fn create_test() { + // Setting up contexts and user context + let (user_context, user) = seed_db().await; + + // Creates the user in the contexts using the 'create' function + let created_user = user_context.create(user.clone()).await.unwrap(); + + let fetched_user = user::Entity::find_by_id(created_user.id) + .one(&user_context.db_context.get_connection()) + .await + .unwrap() + .unwrap(); + + // Assert if the new_user, created_user, and fetched_user are the same + assert_eq!(user, created_user); + assert_eq!(created_user, fetched_user); + } + + #[tokio::test] + async fn create_non_unique_username_test() { + // Setting up contexts and user context + let (user_context, user) = seed_db().await; + + // Creates a model of the user which will be created + let mut users = create_users(2); + + users[0].username = user.clone().username; + users[1].username = user.clone().username; + + // Creates the user in the contexts using the 'create' function + let _created_user1 = user_context.create(users[0].clone()).await.unwrap(); + let created_user2 = user_context.create(users[1].clone()).await; + + assert!(matches!( + created_user2.unwrap_err().sql_err(), + Some(SqlErr::UniqueConstraintViolation(_)) + )); + } + + #[tokio::test] + async fn create_non_unique_email_test() { + // Setting up contexts and user context + let (user_context, user) = seed_db().await; + + // Creates a model of the user which will be created + let mut users = create_users(2); + + users[0].email = user.clone().email; + users[1].email = user.clone().email; + + // Creates the user in the contexts using the 'create' function + let _created_user1 = user_context.create(users[0].clone()).await.unwrap(); + let created_user2 = user_context.create(users[1].clone()).await; + + // Assert if the new_user, created_user, and fetched_user are the same + assert!(matches!( + created_user2.unwrap_err().sql_err(), + Some(SqlErr::UniqueConstraintViolation(_)) + )); + } + + #[tokio::test] + async fn create_auto_increment_test() { + // Setting up contexts and user context + let (user_context, user) = seed_db().await; + + let mut users = create_users(2); + + users[0].id = user.clone().id; + users[1].id = user.clone().id; + + // Creates the user in the contexts using the 'create' function + let created_user1 = user_context.create(users[0].clone()).await.unwrap(); + let created_user2 = user_context.create(users[1].clone()).await.unwrap(); + + let fetched_user1 = user::Entity::find_by_id(created_user1.id) + .one(&user_context.db_context.get_connection()) + .await + .unwrap() + .unwrap(); + + let fetched_user2 = user::Entity::find_by_id(created_user2.id) + .one(&user_context.db_context.get_connection()) + .await + .unwrap() + .unwrap(); + + // Assert if the new_user, created_user, and fetched_user are the same + assert_ne!(fetched_user1.id, fetched_user2.id); + assert_ne!(created_user1.id, created_user2.id); + assert_eq!(created_user1.id, fetched_user1.id); + assert_eq!(created_user2.id, fetched_user2.id); + } + + #[tokio::test] + async fn get_by_id_test() { + // Setting up contexts and user context + let (user_context, user) = seed_db().await; + + // Creates the user in the contexts using the 'create' function + user::Entity::insert(user.clone().into_active_model()) + .exec(&user_context.db_context.get_connection()) + .await + .unwrap(); + + // Fetches the user created using the 'get_by_id' function + let fetched_user = user_context.get_by_id(user.id).await.unwrap().unwrap(); + + // Assert if the new_user, created_user, and fetched_user are the same + assert_eq!(user, fetched_user); + } + + #[tokio::test] + async fn get_by_non_existing_id_test() { + // Setting up contexts and user context + let (user_context, _) = seed_db().await; + + // Fetches the user created using the 'get_by_id' function + let fetched_user = user_context.get_by_id(1).await.unwrap(); + + assert!(fetched_user.is_none()); + } + + #[tokio::test] + async fn get_all_test() { + // Setting up contexts and user context + let (user_context, _) = seed_db().await; + + let users = create_users(10); + let active_users_vec = to_active_models!(users.clone()); + + user::Entity::insert_many(active_users_vec) + .exec(&user_context.db_context.get_connection()) + .await + .unwrap(); + + assert_eq!(user_context.get_all().await.unwrap().len(), 10); + + let mut sorted = users.clone(); + sorted.sort_by_key(|k| k.id); + + for (i, user) in sorted.into_iter().enumerate() { + assert_eq!(user, users[i]); + } + } + + #[tokio::test] + async fn get_all_empty_test() { + // Setting up contexts and user context + let (user_context, _) = seed_db().await; + + let result = user_context.get_all().await.unwrap(); + let empty_users: Vec = vec![]; + + assert_eq!(empty_users, result); + } + + #[tokio::test] + async fn update_test() { + // Setting up contexts and user context + let (user_context, user) = seed_db().await; + + user::Entity::insert(user.clone().into_active_model()) + .exec(&user_context.db_context.get_connection()) + .await + .unwrap(); + + let new_user = user::Model { ..user }; + + let updated_user = user_context.update(new_user.clone()).await.unwrap(); + + let fetched_user = user::Entity::find_by_id(updated_user.id) + .one(&user_context.db_context.get_connection()) + .await + .unwrap() + .unwrap(); + + assert_eq!(new_user, updated_user); + assert_eq!(updated_user, fetched_user); + } + + #[tokio::test] + async fn update_modifies_username_test() { + let (user_context, user) = seed_db().await; + + let user = user::Model { + username: "tester1".into(), + ..user.clone() + }; + + user::Entity::insert(user.clone().into_active_model()) + .exec(&user_context.db_context.get_connection()) + .await + .unwrap(); + + let new_user = user::Model { + username: "tester2".into(), + ..user.clone() + }; + + let updated_user = user_context.update(new_user.clone()).await.unwrap(); + + assert_ne!(user, updated_user); + assert_ne!(user, new_user); + } + + #[tokio::test] + async fn update_modifies_email_test() { + let (user_context, user) = seed_db().await; + + let user = user::Model { + email: "tester1@mail.dk".into(), + ..user.clone() + }; + + user::Entity::insert(user.clone().into_active_model()) + .exec(&user_context.db_context.get_connection()) + .await + .unwrap(); + + let new_user = user::Model { + email: "tester2@mail.dk".into(), + ..user.clone() + }; + + let updated_user = user_context.update(new_user.clone()).await.unwrap(); + + assert_ne!(user, updated_user); + assert_ne!(user, new_user); + } + + #[tokio::test] + async fn update_modifies_password_test() { + let (user_context, user) = seed_db().await; + + let user = user::Model { + password: "12345".into(), + ..user.clone() + }; + + user::Entity::insert(user.clone().into_active_model()) + .exec(&user_context.db_context.get_connection()) + .await + .unwrap(); + + let new_user = user::Model { + password: "123456".into(), + ..user.clone() + }; + + let updated_user = user_context.update(new_user.clone()).await.unwrap(); + + assert_ne!(user, updated_user); + assert_ne!(user, new_user); + } + + #[tokio::test] + async fn update_does_not_modify_id_test() { + let (user_context, user) = seed_db().await; + + user::Entity::insert(user.clone().into_active_model()) + .exec(&user_context.db_context.get_connection()) + .await + .unwrap(); + + let updated_user = user::Model { + id: user.id + 1, + ..user + }; + + let res = user_context.update(updated_user.clone()).await; + + assert!(matches!(res.unwrap_err(), DbErr::RecordNotUpdated)); + } + + #[tokio::test] + async fn update_non_unique_username_test() { + // Setting up contexts and user context + let (user_context, _) = seed_db().await; + + let users = create_users(2); + + user::Entity::insert_many(to_active_models!(users.clone())) + .exec(&user_context.db_context.get_connection()) + .await + .unwrap(); + + let new_user = user::Model { + username: users[1].clone().username, + ..users[0].clone() + }; + + let updated_user = user_context.update(new_user.clone()).await; + + // Assert if the new_user, created_user, and fetched_user are the same + assert!(matches!( + updated_user.unwrap_err().sql_err(), + Some(SqlErr::UniqueConstraintViolation(_)) + )); + } + + #[tokio::test] + async fn update_non_unique_email_test() { + // Setting up contexts and user context + let (user_context, _) = seed_db().await; + + // Creates a model of the user which will be created + let users = create_users(2); + + user::Entity::insert_many(to_active_models!(users.clone())) + .exec(&user_context.db_context.get_connection()) + .await + .unwrap(); + + let new_user = user::Model { + email: users[1].clone().email, + ..users[0].clone() + }; + + let updated_user = user_context.update(new_user.clone()).await; + + // Assert if the new_user, created_user, and fetched_user are the same + assert!(matches!( + updated_user.unwrap_err().sql_err(), + Some(SqlErr::UniqueConstraintViolation(_)) + )); + } + + #[tokio::test] + async fn update_non_existing_id_test() { + // Setting up contexts and user context + let (user_context, user) = seed_db().await; + + let updated_user = user_context.update(user.clone()).await; + + // Assert if the new_user, created_user, and fetched_user are the same + assert!(matches!(updated_user.unwrap_err(), DbErr::RecordNotUpdated)); + } + + #[tokio::test] + async fn delete_test() { + // Setting up contexts and user context + let (user_context, user) = seed_db().await; + + user::Entity::insert(user.clone().into_active_model()) + .exec(&user_context.db_context.get_connection()) + .await + .unwrap(); + + let deleted_user = user_context.delete(user.id).await.unwrap(); + + let all_users = user::Entity::find() + .all(&user_context.db_context.get_connection()) + .await + .unwrap(); + + assert_eq!(user, deleted_user); + assert!(all_users.is_empty()); + } + + #[tokio::test] + async fn delete_cascade_project_test() { + // Setting up contexts and user context + let (user_context, user) = seed_db().await; + + let project = create_projects(1, user.clone().id)[0].clone(); + + user::Entity::insert(user.clone().into_active_model()) + .exec(&user_context.db_context.get_connection()) + .await + .unwrap(); + project::Entity::insert(project.clone().into_active_model()) + .exec(&user_context.db_context.get_connection()) + .await + .unwrap(); + + user_context.delete(user.id).await.unwrap(); + + let all_users = user::Entity::find() + .all(&user_context.db_context.get_connection()) + .await + .unwrap(); + let all_projects = project::Entity::find() + .all(&user_context.db_context.get_connection()) + .await + .unwrap(); + + assert_eq!(all_users.len(), 0); + assert_eq!(all_projects.len(), 0); + } + + #[tokio::test] + async fn delete_cascade_access_test() { + // Setting up contexts and user context + let (user_context, user) = seed_db().await; + + let project = create_projects(1, user.clone().id)[0].clone(); + let access = create_accesses(1, user.clone().id, project.clone().id)[0].clone(); + + user::Entity::insert(user.clone().into_active_model()) + .exec(&user_context.db_context.get_connection()) + .await + .unwrap(); + project::Entity::insert(project.clone().into_active_model()) + .exec(&user_context.db_context.get_connection()) + .await + .unwrap(); + access::Entity::insert(access.clone().into_active_model()) + .exec(&user_context.db_context.get_connection()) + .await + .unwrap(); + + user_context.delete(user.id).await.unwrap(); + + let all_users = user::Entity::find() + .all(&user_context.db_context.get_connection()) + .await + .unwrap(); + let all_projects = project::Entity::find() + .all(&user_context.db_context.get_connection()) + .await + .unwrap(); + let all_accesses = access::Entity::find() + .all(&user_context.db_context.get_connection()) + .await + .unwrap(); + + assert_eq!(all_users.len(), 0); + assert_eq!(all_projects.len(), 0); + assert_eq!(all_accesses.len(), 0); + } + + #[tokio::test] + async fn delete_cascade_session_test() { + // Setting up contexts and user context + let (user_context, user) = seed_db().await; + + let session = create_sessions(1, user.clone().id)[0].clone(); + + user::Entity::insert(user.clone().into_active_model()) + .exec(&user_context.db_context.get_connection()) + .await + .unwrap(); + session::Entity::insert(session.clone().into_active_model()) + .exec(&user_context.db_context.get_connection()) + .await + .unwrap(); + + user_context.delete(user.id).await.unwrap(); + + let all_users = user::Entity::find() + .all(&user_context.db_context.get_connection()) + .await + .unwrap(); + let all_sessions = session::Entity::find() + .all(&user_context.db_context.get_connection()) + .await + .unwrap(); + + assert_eq!(all_users.len(), 0); + assert_eq!(all_sessions.len(), 0); + } + + #[tokio::test] + async fn delete_non_existing_id_test() { + // Setting up contexts and user context + let (user_context, _) = seed_db().await; + + let deleted_user = user_context.delete(1).await; + + // Assert if the new_user, created_user, and fetched_user are the same + assert!(matches!( + deleted_user.unwrap_err(), + DbErr::RecordNotFound(_) + )); + } + + #[tokio::test] + async fn get_by_username_test() { + let (user_context, user) = seed_db().await; + + user::Entity::insert(user.clone().into_active_model()) + .exec(&user_context.db_context.get_connection()) + .await + .unwrap(); + + // Fetches the user created using the 'get_by_username' function + let fetched_user = user_context + .get_by_username(user.username.clone()) + .await + .unwrap(); + + // Assert if the fetched user is the same as the created user + assert_eq!(fetched_user.unwrap().username, user.username); + } + + #[tokio::test] + async fn get_by_email_test() { + let (user_context, user) = seed_db().await; + + user::Entity::insert(user.clone().into_active_model()) + .exec(&user_context.db_context.get_connection()) + .await + .unwrap(); + + let fetched_user = user_context.get_by_email(user.email.clone()).await.unwrap(); + + assert_eq!(fetched_user.unwrap().email, user.email); + } +} diff --git a/src/contexts/mod.rs b/src/contexts/mod.rs index f48d865..40a2112 100644 --- a/src/contexts/mod.rs +++ b/src/contexts/mod.rs @@ -1,3 +1,126 @@ pub mod context_collection; pub mod context_impls; pub mod context_traits; + +#[cfg(test)] +mod helpers { + use crate::contexts::context_impls::{PostgresDatabaseContext, SQLiteDatabaseContext}; + use crate::contexts::context_traits::DatabaseContextTrait; + use crate::entities::{access, in_use, project, query, session, user}; + use dotenv::dotenv; + use sea_orm::{ConnectionTrait, Database, DbBackend}; + use std::env; + use std::sync::Arc; + + pub async fn get_reset_database_context() -> Arc { + dotenv().ok(); + + let url = + env::var("TEST_DATABASE_URL").expect("TEST_DATABASE_URL must be set to run tests."); + let db = Database::connect(&url).await.unwrap(); + let db_context: Arc = match db.get_database_backend() { + DbBackend::Sqlite => Arc::new(SQLiteDatabaseContext::new(&url).await.unwrap()), + DbBackend::Postgres => Arc::new(PostgresDatabaseContext::new(&url).await.unwrap()), + _ => panic!("Database protocol not supported"), + }; + + db_context.reset().await.unwrap() + } + + /// + /// + /// # Arguments + /// + /// * `amount`: + /// * `model`: + /// + /// returns: Vec + /// + /// # Examples + /// + /// ``` + /// let vector: Vec = create_entities(3,|x| UserModel { + /// id: &x+i, + /// email: format!("mail{}@mail.dk",&x), + /// username: format!("username{}", &x), + /// password: format!("qwerty{}", &x), + /// ); + /// ``` + + pub fn create_entities(amount: i32, project_creator: F) -> Vec + where + F: Fn(i32) -> M, + { + let mut vector: Vec = vec![]; + for i in 0..amount { + vector.push(project_creator(i)); + } + vector + } + + pub fn create_users(amount: i32) -> Vec { + create_entities(amount, |i| user::Model { + id: i + 1, + email: format!("mail{}@mail.dk", &i), + username: format!("username{}", &i), + password: format!("qwerty{}", &i), + }) + } + + pub fn create_projects(amount: i32, user_id: i32) -> Vec { + create_entities(amount, |i| project::Model { + id: i + 1, + name: format!("name {}", i), + components_info: "{}".to_owned().parse().unwrap(), + owner_id: user_id, + }) + } + + pub fn create_accesses(amount: i32, user_id: i32, project_id: i32) -> Vec { + create_entities(amount, |i| access::Model { + id: i + 1, + role: "Reader".into(), + project_id: project_id + i, + user_id: user_id + i, + }) + } + + pub fn create_sessions(amount: i32, user_id: i32) -> Vec { + create_entities(amount, |i| session::Model { + id: i + 1, + refresh_token: "test_refresh_token".to_string() + format!("{}", i).as_str(), + access_token: "test_access_token".to_string() + format!("{}", i).as_str(), + user_id, + updated_at: Default::default(), + }) + } + + pub fn create_in_uses(amount: i32, project_id: i32, session_id: i32) -> Vec { + create_entities(amount, |i| in_use::Model { + project_id: project_id + i, + session_id, + latest_activity: Default::default(), + }) + } + + pub fn create_queries(amount: i32, project_id: i32) -> Vec { + create_entities(amount, |i| query::Model { + id: i + 1, + string: "".to_string(), + result: None, + outdated: true, + project_id, + }) + } + + #[macro_export] + macro_rules! to_active_models { + ($vec:expr) => {{ + let mut models = Vec::new(); + for model in $vec { + models.push(model.into_active_model()); + } + models + }}; + } +} diff --git a/src/controllers/controller_impls/access_controller.rs b/src/controllers/controller_impls/access_controller.rs index 816b9e0..325a749 100644 --- a/src/controllers/controller_impls/access_controller.rs +++ b/src/controllers/controller_impls/access_controller.rs @@ -305,5 +305,528 @@ async fn create_access_find_user_helper( } #[cfg(test)] -#[path = "../../tests/controllers/access_controller.rs"] -mod access_controller_tests; +mod tests { + use super::super::helpers::{disguise_context_mocks, get_mock_contexts}; + use crate::api::server::protobuf::create_access_request::User; + use crate::api::server::protobuf::{ + AccessInfo, CreateAccessRequest, DeleteAccessRequest, ListAccessInfoRequest, + UpdateAccessRequest, + }; + use crate::controllers::controller_impls::AccessController; + use crate::controllers::controller_traits::AccessControllerTrait; + use crate::entities::{access, project, user}; + use mockall::predicate; + use sea_orm::DbErr; + use std::str::FromStr; + use tonic::{metadata, Code, Request}; + + #[tokio::test] + async fn create_invalid_access_returns_err() { + let mut mock_contexts = get_mock_contexts(); + + let access = access::Model { + id: Default::default(), + role: "Editor".to_string(), + project_id: 1, + user_id: 1, + }; + + mock_contexts + .access_context_mock + .expect_create() + .with(predicate::eq(access.clone())) + .returning(move |_| Err(DbErr::RecordNotInserted)); + + mock_contexts + .access_context_mock + .expect_get_access_by_uid_and_project_id() + .with(predicate::eq(1), predicate::eq(1)) + .returning(move |_, _| { + Ok(Some(access::Model { + id: Default::default(), + role: "Editor".to_owned(), + user_id: 1, + project_id: 1, + })) + }); + + mock_contexts + .user_context_mock + .expect_get_by_id() + .with(predicate::eq(1)) + .returning(move |_| { + Ok(Some(user::Model { + id: 1, + email: Default::default(), + username: "test".to_string(), + password: "test".to_string(), + })) + }); + + let mut request = Request::new(CreateAccessRequest { + role: "Editor".to_string(), + project_id: 1, + user: Some(User::UserId(1)), + }); + + request.metadata_mut().insert( + "uid", + tonic::metadata::MetadataValue::from_str("1").unwrap(), + ); + + let contexts = disguise_context_mocks(mock_contexts); + let access_logic = AccessController::new(contexts); + + let res = access_logic.create_access(request).await.unwrap_err(); + + assert_eq!(res.code(), Code::Internal); + } + + #[tokio::test] + async fn create_access_returns_ok() { + let mut mock_contexts = get_mock_contexts(); + + let access = access::Model { + id: Default::default(), + role: "Editor".to_string(), + project_id: 1, + user_id: 1, + }; + + mock_contexts + .access_context_mock + .expect_get_access_by_uid_and_project_id() + .with(predicate::eq(1), predicate::eq(1)) + .returning(move |_, _| { + Ok(Some(access::Model { + id: Default::default(), + role: "Editor".to_string(), + user_id: 1, + project_id: 1, + })) + }); + + mock_contexts + .access_context_mock + .expect_create() + .with(predicate::eq(access.clone())) + .returning(move |_| Ok(access.clone())); + + mock_contexts + .user_context_mock + .expect_get_by_id() + .with(predicate::eq(1)) + .returning(move |_| { + Ok(Some(user::Model { + id: 1, + email: Default::default(), + username: "test".to_string(), + password: "test".to_string(), + })) + }); + + let mut request = Request::new(CreateAccessRequest { + role: "Editor".to_string(), + project_id: 1, + user: Some(User::UserId(1)), + }); + + request.metadata_mut().insert( + "uid", + tonic::metadata::MetadataValue::from_str("1").unwrap(), + ); + + let contexts = disguise_context_mocks(mock_contexts); + let access_logic = AccessController::new(contexts); + + let res = access_logic.create_access(request).await; + + assert!(res.is_ok()); + } + + #[tokio::test] + async fn update_invalid_access_returns_err() { + let mut mock_contexts = get_mock_contexts(); + + let access = access::Model { + id: 2, + role: "Editor".to_string(), + project_id: Default::default(), + user_id: Default::default(), + }; + + mock_contexts + .access_context_mock + .expect_update() + .with(predicate::eq(access.clone())) + .returning(move |_| Err(DbErr::RecordNotUpdated)); + + mock_contexts + .access_context_mock + .expect_get_by_id() + .with(predicate::eq(2)) + .returning(move |_| { + Ok(Some(access::Model { + id: 1, + role: "Editor".to_string(), + project_id: 1, + user_id: 2, + })) + }); + + mock_contexts + .access_context_mock + .expect_get_access_by_uid_and_project_id() + .with(predicate::eq(1), predicate::eq(1)) + .returning(move |_, _| { + Ok(Some(access::Model { + id: 1, + role: "Editor".to_string(), + project_id: 1, + user_id: 1, + })) + }); + + mock_contexts + .project_context_mock + .expect_get_by_id() + .with(predicate::eq(1)) + .returning(move |_| { + Ok(Some(project::Model { + id: 1, + name: "test".to_string(), + owner_id: 1, + components_info: Default::default(), + })) + }); + + let mut request = Request::new(UpdateAccessRequest { + id: 2, + role: "Editor".to_string(), + }); + + request.metadata_mut().insert( + "uid", + tonic::metadata::MetadataValue::from_str("1").unwrap(), + ); + + let contexts = disguise_context_mocks(mock_contexts); + let access_logic = AccessController::new(contexts); + + let res = access_logic.update_access(request).await.unwrap_err(); + + assert_eq!(res.code(), Code::Internal); + } + + #[tokio::test] + async fn update_access_returns_ok() { + let mut mock_contexts = get_mock_contexts(); + + let access = access::Model { + id: 2, + role: "Editor".to_string(), + project_id: Default::default(), + user_id: Default::default(), + }; + + mock_contexts + .access_context_mock + .expect_update() + .with(predicate::eq(access.clone())) + .returning(move |_| Ok(access.clone())); + + mock_contexts + .access_context_mock + .expect_get_by_id() + .with(predicate::eq(2)) + .returning(move |_| { + Ok(Some(access::Model { + id: 1, + role: "Editor".to_string(), + project_id: 1, + user_id: 2, + })) + }); + + mock_contexts + .access_context_mock + .expect_get_access_by_uid_and_project_id() + .with(predicate::eq(1), predicate::eq(1)) + .returning(move |_, _| { + Ok(Some(access::Model { + id: 1, + role: "Editor".to_string(), + project_id: 1, + user_id: 1, + })) + }); + + mock_contexts + .project_context_mock + .expect_get_by_id() + .with(predicate::eq(1)) + .returning(move |_| { + Ok(Some(project::Model { + id: 1, + name: "test".to_string(), + owner_id: 1, + components_info: Default::default(), + })) + }); + + let mut request = Request::new(UpdateAccessRequest { + id: 2, + role: "Editor".to_string(), + }); + + request.metadata_mut().insert( + "uid", + tonic::metadata::MetadataValue::from_str("1").unwrap(), + ); + + let contexts = disguise_context_mocks(mock_contexts); + let access_logic = AccessController::new(contexts); + + let res = access_logic.update_access(request).await; + + print!("{:?}", res); + + assert!(res.is_ok()); + } + + #[tokio::test] + async fn delete_invalid_access_returns_err() { + let mut mock_contexts = get_mock_contexts(); + + mock_contexts + .access_context_mock + .expect_delete() + .with(predicate::eq(2)) + .returning(move |_| Err(DbErr::RecordNotFound("".to_string()))); + + mock_contexts + .access_context_mock + .expect_get_by_id() + .with(predicate::eq(2)) + .returning(move |_| { + Ok(Some(access::Model { + id: 1, + role: "Editor".to_string(), + project_id: 1, + user_id: 2, + })) + }); + + mock_contexts + .access_context_mock + .expect_get_access_by_uid_and_project_id() + .with(predicate::eq(1), predicate::eq(1)) + .returning(move |_, _| { + Ok(Some(access::Model { + id: 1, + role: "Editor".to_string(), + project_id: 1, + user_id: 1, + })) + }); + + mock_contexts + .project_context_mock + .expect_get_by_id() + .with(predicate::eq(1)) + .returning(move |_| { + Ok(Some(project::Model { + id: 1, + name: "test".to_string(), + owner_id: 1, + components_info: Default::default(), + })) + }); + + let mut request = Request::new(DeleteAccessRequest { id: 2 }); + + request.metadata_mut().insert( + "uid", + tonic::metadata::MetadataValue::from_str("1").unwrap(), + ); + + let contexts = disguise_context_mocks(mock_contexts); + let access_logic = AccessController::new(contexts); + + let res = access_logic.delete_access(request).await.unwrap_err(); + + assert_eq!(res.code(), Code::NotFound); + } + + #[tokio::test] + async fn delete_access_returns_ok() { + let mut mock_contexts = get_mock_contexts(); + + let access = access::Model { + id: 2, + role: "Editor".to_string(), + project_id: Default::default(), + user_id: Default::default(), + }; + + mock_contexts + .access_context_mock + .expect_delete() + .with(predicate::eq(2)) + .returning(move |_| Ok(access.clone())); + + mock_contexts + .access_context_mock + .expect_get_by_id() + .with(predicate::eq(2)) + .returning(move |_| { + Ok(Some(access::Model { + id: 1, + role: "Editor".to_string(), + project_id: 1, + user_id: 2, + })) + }); + + mock_contexts + .access_context_mock + .expect_get_access_by_uid_and_project_id() + .with(predicate::eq(1), predicate::eq(1)) + .returning(move |_, _| { + Ok(Some(access::Model { + id: 1, + role: "Editor".to_string(), + project_id: 1, + user_id: 1, + })) + }); + + mock_contexts + .project_context_mock + .expect_get_by_id() + .with(predicate::eq(1)) + .returning(move |_| { + Ok(Some(project::Model { + id: 1, + name: "test".to_string(), + owner_id: 1, + components_info: Default::default(), + })) + }); + + let mut request = Request::new(DeleteAccessRequest { id: 2 }); + + request.metadata_mut().insert( + "uid", + tonic::metadata::MetadataValue::from_str("1").unwrap(), + ); + + let contexts = disguise_context_mocks(mock_contexts); + let access_logic = AccessController::new(contexts); + + let res = access_logic.delete_access(request).await; + + assert!(res.is_ok()); + } + + #[tokio::test] + async fn list_access_info_returns_ok() { + let mut mock_contexts = get_mock_contexts(); + + let mut request: Request = + Request::new(ListAccessInfoRequest { project_id: 1 }); + + request + .metadata_mut() + .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); + + let access = AccessInfo { + id: 1, + role: "Editor".to_string(), + project_id: 1, + user_id: 1, + }; + + mock_contexts + .access_context_mock + .expect_get_access_by_uid_and_project_id() + .returning(move |_, _| { + Ok(Some(access::Model { + id: 1, + role: "Editor".to_string(), + project_id: Default::default(), + user_id: Default::default(), + })) + }); + + mock_contexts + .access_context_mock + .expect_get_access_by_project_id() + .returning(move |_| Ok(vec![access.clone()])); + + let contexts = disguise_context_mocks(mock_contexts); + let access_logic = AccessController::new(contexts); + + let res = access_logic.list_access_info(request).await; + + assert!(res.is_ok()); + } + + #[tokio::test] + async fn list_access_info_returns_not_found() { + let mut mock_contexts = get_mock_contexts(); + + let mut request = Request::new(ListAccessInfoRequest { project_id: 1 }); + + request + .metadata_mut() + .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); + + let access = access::Model { + id: 1, + role: "Editor".to_string(), + project_id: 1, + user_id: 1, + }; + + mock_contexts + .access_context_mock + .expect_get_access_by_project_id() + .returning(move |_| Ok(vec![])); + + mock_contexts + .access_context_mock + .expect_get_access_by_uid_and_project_id() + .returning(move |_, _| Ok(Some(access.clone()))); + + let contexts = disguise_context_mocks(mock_contexts); + let access_logic = AccessController::new(contexts); + + let res = access_logic.list_access_info(request).await.unwrap_err(); + + assert_eq!(res.code(), Code::NotFound); + } + + #[tokio::test] + async fn list_access_info_returns_no_permission() { + let mut request = Request::new(ListAccessInfoRequest { project_id: 1 }); + + request + .metadata_mut() + .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); + + let mut mock_contexts = get_mock_contexts(); + + mock_contexts + .access_context_mock + .expect_get_access_by_uid_and_project_id() + .returning(move |_, _| Ok(None)); + + let contexts = disguise_context_mocks(mock_contexts); + let access_logic = AccessController::new(contexts); + + let res = access_logic.list_access_info(request).await.unwrap_err(); + + assert_eq!(res.code(), Code::PermissionDenied); + } +} diff --git a/src/controllers/controller_impls/mod.rs b/src/controllers/controller_impls/mod.rs index 016f5fc..c2c25b8 100644 --- a/src/controllers/controller_impls/mod.rs +++ b/src/controllers/controller_impls/mod.rs @@ -11,3 +11,199 @@ pub use query_controller::QueryController; pub use reveaal_controller::ReveaalController; pub use session_controller::SessionController; pub use user_controller::UserController; + +#[cfg(test)] +mod helpers { + use crate::api::auth::TokenType; + use crate::api::server::protobuf::AccessInfo; + use crate::api::server::protobuf::ProjectInfo; + use crate::api::server::protobuf::{ + QueryRequest, QueryResponse, SimulationStartRequest, SimulationStepRequest, + SimulationStepResponse, UserTokenResponse, + }; + use crate::contexts::context_collection::ContextCollection; + use crate::contexts::context_traits::*; + use crate::entities::{access, in_use, project, query, session, user}; + use crate::services::service_collection::ServiceCollection; + use crate::services::service_traits::*; + use async_trait::async_trait; + use mockall::mock; + use sea_orm::DbErr; + use std::sync::Arc; + use tonic::{Request, Response, Status}; + + pub fn get_mock_contexts() -> MockContexts { + MockContexts { + access_context_mock: MockAccessContext::new(), + in_use_context_mock: MockInUseContext::new(), + project_context_mock: MockProjectContext::new(), + query_context_mock: MockQueryContext::new(), + session_context_mock: MockSessionContext::new(), + user_context_mock: MockUserContext::new(), + } + } + + pub fn get_mock_services() -> MockServices { + MockServices { + hashing_service_mock: MockHashingService::new(), + reveaal_service_mock: MockReveaalService::new(), + } + } + + pub fn disguise_context_mocks(mock_services: MockContexts) -> ContextCollection { + ContextCollection { + access_context: Arc::new(mock_services.access_context_mock), + in_use_context: Arc::new(mock_services.in_use_context_mock), + project_context: Arc::new(mock_services.project_context_mock), + query_context: Arc::new(mock_services.query_context_mock), + session_context: Arc::new(mock_services.session_context_mock), + user_context: Arc::new(mock_services.user_context_mock), + } + } + + pub fn disguise_service_mocks(mock_services: MockServices) -> ServiceCollection { + ServiceCollection { + hashing_service: Arc::new(mock_services.hashing_service_mock), + reveaal_service: Arc::new(mock_services.reveaal_service_mock), + } + } + + pub struct MockContexts { + pub(crate) access_context_mock: MockAccessContext, + pub(crate) in_use_context_mock: MockInUseContext, + pub(crate) project_context_mock: MockProjectContext, + pub(crate) query_context_mock: MockQueryContext, + pub(crate) session_context_mock: MockSessionContext, + pub(crate) user_context_mock: MockUserContext, + } + + pub struct MockServices { + pub(crate) hashing_service_mock: MockHashingService, + pub(crate) reveaal_service_mock: MockReveaalService, + } + + mock! { + pub AccessContext {} + #[async_trait] + impl EntityContextTrait for AccessContext { + async fn create(&self, entity: access::Model) -> Result; + async fn get_by_id(&self, entity_id: i32) -> Result, DbErr>; + async fn get_all(&self) -> Result, DbErr>; + async fn update(&self, entity: access::Model) -> Result; + async fn delete(&self, entity_id: i32) -> Result; + } + #[async_trait] + impl AccessContextTrait for AccessContext { + async fn get_access_by_uid_and_project_id( + &self, + uid: i32, + project_id: i32, + ) -> Result, DbErr>; + + async fn get_access_by_project_id( + &self, + project_id: i32, + ) -> Result, DbErr>; + } + } + + mock! { + pub InUseContext {} + #[async_trait] + impl EntityContextTrait for InUseContext { + async fn create(&self, entity: in_use::Model) -> Result; + async fn get_by_id(&self, entity_id: i32) -> Result, DbErr>; + async fn get_all(&self) -> Result, DbErr>; + async fn update(&self, entity: in_use::Model) -> Result; + async fn delete(&self, entity_id: i32) -> Result; + } + #[async_trait] + impl InUseContextTrait for InUseContext {} + } + + mock! { + pub ProjectContext {} + #[async_trait] + impl EntityContextTrait for ProjectContext { + async fn create(&self, entity: project::Model) -> Result; + async fn get_by_id(&self, entity_id: i32) -> Result, DbErr>; + async fn get_all(&self) -> Result, DbErr>; + async fn update(&self, entity: project::Model) -> Result; + async fn delete(&self, entity_id: i32) -> Result; + } + #[async_trait] + impl ProjectContextTrait for ProjectContext { + async fn get_project_info_by_uid(&self, uid: i32) -> Result, DbErr>; + } + } + + mock! { + pub QueryContext {} + #[async_trait] + impl EntityContextTrait for QueryContext { + async fn create(&self, entity: query::Model) -> Result; + async fn get_by_id(&self, entity_id: i32) -> Result, DbErr>; + async fn get_all(&self) -> Result, DbErr>; + async fn update(&self, entity: query::Model) -> Result; + async fn delete(&self, entity_id: i32) -> Result; + } + #[async_trait] + impl QueryContextTrait for QueryContext { + async fn get_all_by_project_id(&self, project_id: i32) -> Result, DbErr>; + } + } + + mock! { + pub SessionContext {} + #[async_trait] + impl EntityContextTrait for SessionContext { + async fn create(&self, entity: session::Model) -> Result; + async fn get_by_id(&self, entity_id: i32) -> Result, DbErr>; + async fn get_all(&self) -> Result, DbErr>; + async fn update(&self, entity: session::Model) -> Result; + async fn delete(&self, entity_id: i32) -> Result; + } + #[async_trait] + impl SessionContextTrait for SessionContext { + async fn get_by_token(&self, token_type: TokenType, token: String) -> Result, DbErr>; + async fn delete_by_token(&self, token_type: TokenType, token: String) -> Result; + } + } + + mock! { + pub UserContext {} + #[async_trait] + impl EntityContextTrait for UserContext { + async fn create(&self, entity: user::Model) -> Result; + async fn get_by_id(&self, entity_id: i32) -> Result, DbErr>; + async fn get_all(&self) -> Result, DbErr>; + async fn update(&self, entity: user::Model) -> Result; + async fn delete(&self, entity_id: i32) -> Result; + } + #[async_trait] + impl UserContextTrait for UserContext { + async fn get_by_username(&self, username: String) -> Result, DbErr>; + async fn get_by_email(&self, email: String) -> Result, DbErr>; + async fn get_by_ids(&self, ids: Vec) -> Result, DbErr>; + } + } + + mock! { + pub ReveaalService{} + #[async_trait] + impl ReveaalServiceTrait for ReveaalService { + async fn get_user_token(&self,request: Request<()>) -> Result, Status>; + async fn send_query(&self,request: Request) -> Result, Status>; + async fn start_simulation(&self, request: Request) -> Result, Status>; + async fn take_simulation_step(&self, request: Request) -> Result, Status>; + } + } + + mock! { + pub HashingService {} + impl HashingServiceTrait for HashingService { + fn hash_password(&self, password: String) -> Result; + fn verify_password(&self, password: String, hash: &str) -> Result; + } + } +} diff --git a/src/controllers/controller_impls/project_controller.rs b/src/controllers/controller_impls/project_controller.rs index 54a3ba2..2c14ca5 100644 --- a/src/controllers/controller_impls/project_controller.rs +++ b/src/controllers/controller_impls/project_controller.rs @@ -490,5 +490,1525 @@ impl ProjectControllerTrait for ProjectController { } #[cfg(test)] -#[path = "../../tests/controllers/project_controller.rs"] -mod project_controller_tests; +mod tests { + use super::super::helpers::disguise_context_mocks; + use super::super::helpers::get_mock_contexts; + use crate::controllers::controller_impls::ProjectController; + use crate::controllers::controller_traits::ProjectControllerTrait; + use crate::{ + api::{ + auth::TokenType, + server::protobuf::{ + component::Rep, Component, ComponentsInfo, CreateProjectRequest, + DeleteProjectRequest, GetProjectRequest, ProjectInfo, UpdateProjectRequest, + }, + }, + entities::{access, in_use, project, query, session}, + }; + use chrono::Utc; + use mockall::predicate; + use sea_orm::DbErr; + use std::str::FromStr; + use tonic::{metadata, Code, Request}; + + #[tokio::test] + async fn create_project_returns_ok() { + let mut mock_contexts = get_mock_contexts(); + + let uid = 0; + + let components_info = ComponentsInfo { + components: vec![], + components_hash: 0, + }; + + let project = project::Model { + id: Default::default(), + name: Default::default(), + components_info: serde_json::to_value(components_info.clone()).unwrap(), + owner_id: uid, + }; + + let access = access::Model { + id: Default::default(), + role: "Editor".to_string(), + user_id: uid, + project_id: project.id, + }; + + let session = session::Model { + id: Default::default(), + refresh_token: "refresh_token".to_string(), + access_token: "access_token".to_string(), + updated_at: Default::default(), + user_id: uid, + }; + + let in_use = in_use::Model { + project_id: project.id, + session_id: session.id, + latest_activity: Default::default(), + }; + + mock_contexts + .project_context_mock + .expect_create() + .with(predicate::eq(project.clone())) + .returning(move |_| Ok(project.clone())); + + mock_contexts + .access_context_mock + .expect_create() + .with(predicate::eq(access.clone())) + .returning(move |_| Ok(access.clone())); + + mock_contexts + .session_context_mock + .expect_get_by_token() + .with( + predicate::eq(TokenType::AccessToken), + predicate::eq("access_token".to_string()), + ) + .returning(move |_, _| Ok(Some(session.clone()))); + + mock_contexts + .in_use_context_mock + .expect_create() + .with(predicate::eq(in_use.clone())) + .returning(move |_| Ok(in_use.clone())); + + let mut request = Request::new(CreateProjectRequest { + name: Default::default(), + components_info: Option::from(components_info), + }); + + request + .metadata_mut() + .insert("uid", uid.to_string().parse().unwrap()); + + request.metadata_mut().insert( + "authorization", + metadata::MetadataValue::from_str("Bearer access_token").unwrap(), + ); + + let contexts = disguise_context_mocks(mock_contexts); + let project_logic = ProjectController::new(contexts); + + let res = project_logic.create_project(request).await; + + assert!(res.is_ok()); + } + + #[tokio::test] + async fn create_project_existing_name_returns_err() { + let mut mock_contexts = get_mock_contexts(); + + let uid = 0; + + let project = project::Model { + id: Default::default(), + name: "project".to_string(), + components_info: Default::default(), + owner_id: uid, + }; + + mock_contexts + .project_context_mock + .expect_create() + .with(predicate::eq(project.clone())) + .returning(move |_| Err(DbErr::RecordNotInserted)); //todo!("Needs to be a SqlError with UniqueConstraintViolation with 'name' in message) + + let mut request = Request::new(CreateProjectRequest { + name: "project".to_string(), + components_info: Default::default(), + }); + + request + .metadata_mut() + .insert("uid", uid.to_string().parse().unwrap()); + + let contexts = disguise_context_mocks(mock_contexts); + let project_logic = ProjectController::new(contexts); + + let res = project_logic.create_project(request).await; + + assert_eq!(res.unwrap_err().code(), Code::InvalidArgument); //todo!("Needs to be code AlreadyExists when mocked Error is corrected) + } + + #[tokio::test] + async fn get_project_user_has_access_returns_ok() { + let mut mock_contexts = get_mock_contexts(); + + let project = project::Model { + id: Default::default(), + name: "project".to_string(), + components_info: Default::default(), + owner_id: 0, + }; + + let access = access::Model { + id: Default::default(), + role: "Editor".to_string(), + project_id: 1, + user_id: 1, + }; + + let in_use = in_use::Model { + project_id: Default::default(), + session_id: 0, + latest_activity: Utc::now().naive_utc(), + }; + + let queries: Vec = vec![]; + + mock_contexts + .access_context_mock + .expect_get_access_by_uid_and_project_id() + .with(predicate::eq(0), predicate::eq(0)) + .returning(move |_, _| Ok(Some(access.clone()))); + + mock_contexts + .project_context_mock + .expect_get_by_id() + .with(predicate::eq(0)) + .returning(move |_| Ok(Some(project.clone()))); + + mock_contexts + .in_use_context_mock + .expect_get_by_id() + .with(predicate::eq(0)) + .returning(move |_| Ok(Some(in_use.clone()))); + + mock_contexts + .query_context_mock + .expect_get_all_by_project_id() + .with(predicate::eq(0)) + .returning(move |_| Ok(queries.clone())); + + let mut request = Request::new(GetProjectRequest { id: 0 }); + + request.metadata_mut().insert("uid", "0".parse().unwrap()); + + let contexts = disguise_context_mocks(mock_contexts); + let project_logic = ProjectController::new(contexts); + + let res = project_logic.get_project(request).await; + + assert!(res.is_ok()); + } + + #[tokio::test] + async fn delete_not_owner_returns_err() { + let mut mock_contexts = get_mock_contexts(); + + mock_contexts + .project_context_mock + .expect_get_by_id() + .with(predicate::eq(1)) + .returning(move |_| { + Ok(Some(project::Model { + id: 1, + name: Default::default(), + components_info: Default::default(), + owner_id: 2, + })) + }); + + let mut request = Request::new(DeleteProjectRequest { id: 1 }); + + request + .metadata_mut() + .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); + + let contexts = disguise_context_mocks(mock_contexts); + let project_logic = ProjectController::new(contexts); + + let res = project_logic.delete_project(request).await.unwrap_err(); + + assert_eq!(res.code(), Code::PermissionDenied); + } + + #[tokio::test] + async fn delete_invalid_project_returns_err() { + let mut mock_contexts = get_mock_contexts(); + + mock_contexts + .project_context_mock + .expect_get_by_id() + .with(predicate::eq(2)) + .returning(move |_| Ok(None)); + + let mut request = Request::new(DeleteProjectRequest { id: 2 }); + + request + .metadata_mut() + .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); + + let contexts = disguise_context_mocks(mock_contexts); + let project_logic = ProjectController::new(contexts); + + let res = project_logic.delete_project(request).await.unwrap_err(); + + assert_eq!(res.code(), Code::NotFound); + } + + #[tokio::test] + async fn delete_project_returns_ok() { + let mut mock_contexts = get_mock_contexts(); + + mock_contexts + .project_context_mock + .expect_get_by_id() + .with(predicate::eq(1)) + .returning(move |_| { + Ok(Some(project::Model { + id: 1, + name: Default::default(), + components_info: Default::default(), + owner_id: 1, + })) + }); + + mock_contexts + .project_context_mock + .expect_delete() + .with(predicate::eq(1)) + .returning(move |_| { + Ok(project::Model { + id: 1, + name: Default::default(), + components_info: Default::default(), + owner_id: 1, + }) + }); + + let mut request = Request::new(DeleteProjectRequest { id: 1 }); + + request + .metadata_mut() + .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); + + let contexts = disguise_context_mocks(mock_contexts); + let project_logic = ProjectController::new(contexts); + + let res = project_logic.delete_project(request).await; + + assert!(res.is_ok()); + } + + #[tokio::test] + async fn get_project_user_has_no_access_returns_err() { + let mut mock_contexts = get_mock_contexts(); + + let project = project::Model { + id: Default::default(), + name: "project".to_string(), + components_info: Default::default(), + owner_id: 0, + }; + + let in_use = in_use::Model { + project_id: Default::default(), + session_id: 0, + latest_activity: Default::default(), + }; + + let queries: Vec = vec![]; + + mock_contexts + .access_context_mock + .expect_get_access_by_uid_and_project_id() + .with(predicate::eq(0), predicate::eq(0)) + .returning(move |_, _| Ok(None)); + + mock_contexts + .project_context_mock + .expect_get_by_id() + .with(predicate::eq(0)) + .returning(move |_| Ok(Some(project.clone()))); + + mock_contexts + .in_use_context_mock + .expect_get_by_id() + .with(predicate::eq(0)) + .returning(move |_| Ok(Some(in_use.clone()))); + + mock_contexts + .query_context_mock + .expect_get_all_by_project_id() + .with(predicate::eq(0)) + .returning(move |_| Ok(queries.clone())); + + let mut request = Request::new(GetProjectRequest { id: 0 }); + + request.metadata_mut().insert("uid", "0".parse().unwrap()); + + let contexts = disguise_context_mocks(mock_contexts); + let project_logic = ProjectController::new(contexts); + + let res = project_logic.get_project(request).await.unwrap_err(); + + assert!(res.code() == Code::PermissionDenied); + } + + #[tokio::test] + async fn get_project_is_in_use_is_true() { + let mut mock_contexts = get_mock_contexts(); + + let project = project::Model { + id: Default::default(), + name: "project".to_string(), + components_info: Default::default(), + owner_id: 0, + }; + + let access = access::Model { + id: Default::default(), + role: "Editor".to_string(), + project_id: 1, + user_id: 1, + }; + + let in_use = in_use::Model { + project_id: Default::default(), + session_id: 0, + latest_activity: Utc::now().naive_utc(), + }; + + let queries: Vec = vec![]; + + mock_contexts + .access_context_mock + .expect_get_access_by_uid_and_project_id() + .with(predicate::eq(0), predicate::eq(0)) + .returning(move |_, _| Ok(Some(access.clone()))); + + mock_contexts + .project_context_mock + .expect_get_by_id() + .with(predicate::eq(0)) + .returning(move |_| Ok(Some(project.clone()))); + + mock_contexts + .in_use_context_mock + .expect_get_by_id() + .with(predicate::eq(0)) + .returning(move |_| Ok(Some(in_use.clone()))); + + mock_contexts + .query_context_mock + .expect_get_all_by_project_id() + .with(predicate::eq(0)) + .returning(move |_| Ok(queries.clone())); + + let mut request = Request::new(GetProjectRequest { id: 0 }); + + request.metadata_mut().insert("uid", "0".parse().unwrap()); + + let contexts = disguise_context_mocks(mock_contexts); + let project_logic = ProjectController::new(contexts); + + let res = project_logic.get_project(request).await; + + assert!(res.unwrap().get_ref().in_use); + } + + #[tokio::test] + async fn get_project_is_in_use_is_false() { + let mut mock_contexts = get_mock_contexts(); + + let project = project::Model { + id: Default::default(), + name: "project".to_string(), + components_info: Default::default(), + owner_id: 0, + }; + + let access = access::Model { + id: Default::default(), + role: "Editor".to_string(), + project_id: 1, + user_id: 1, + }; + + let in_use = in_use::Model { + project_id: 0, + session_id: 0, + latest_activity: Default::default(), + }; + + let updated_in_use = in_use::Model { + project_id: 0, + session_id: 1, + latest_activity: Default::default(), + }; + + let session = session::Model { + id: 0, + refresh_token: "refresh_token".to_owned(), + access_token: "access_token".to_owned(), + updated_at: Default::default(), + user_id: Default::default(), + }; + + let queries: Vec = vec![]; + + mock_contexts + .access_context_mock + .expect_get_access_by_uid_and_project_id() + .with(predicate::eq(0), predicate::eq(0)) + .returning(move |_, _| Ok(Some(access.clone()))); + + mock_contexts + .project_context_mock + .expect_get_by_id() + .with(predicate::eq(0)) + .returning(move |_| Ok(Some(project.clone()))); + + mock_contexts + .in_use_context_mock + .expect_get_by_id() + .with(predicate::eq(0)) + .returning(move |_| Ok(Some(in_use.clone()))); + + mock_contexts + .session_context_mock + .expect_get_by_token() + .with( + predicate::eq(TokenType::AccessToken), + predicate::eq("access_token".to_owned()), + ) + .returning(move |_, _| Ok(Some(session.clone()))); + + mock_contexts + .query_context_mock + .expect_get_all_by_project_id() + .with(predicate::eq(0)) + .returning(move |_| Ok(queries.clone())); + + mock_contexts + .in_use_context_mock + .expect_update() + .returning(move |_| Ok(updated_in_use.clone())); + + let mut request = Request::new(GetProjectRequest { id: 0 }); + + request + .metadata_mut() + .insert("authorization", "Bearer access_token".parse().unwrap()); + request.metadata_mut().insert("uid", "0".parse().unwrap()); + + let contexts = disguise_context_mocks(mock_contexts); + let project_logic = ProjectController::new(contexts); + + let res = project_logic.get_project(request).await; + + assert!(!res.unwrap().get_ref().in_use); + } + + #[tokio::test] + async fn get_project_project_has_no_queries_queries_are_empty() { + let mut mock_contexts = get_mock_contexts(); + + let project = project::Model { + id: Default::default(), + name: "project".to_string(), + components_info: Default::default(), + owner_id: 0, + }; + + let access = access::Model { + id: Default::default(), + role: "Editor".to_string(), + project_id: 1, + user_id: 1, + }; + + let in_use = in_use::Model { + project_id: Default::default(), + session_id: 0, + latest_activity: Utc::now().naive_utc(), + }; + + let queries: Vec = vec![]; + + mock_contexts + .access_context_mock + .expect_get_access_by_uid_and_project_id() + .with(predicate::eq(0), predicate::eq(0)) + .returning(move |_, _| Ok(Some(access.clone()))); + + mock_contexts + .project_context_mock + .expect_get_by_id() + .with(predicate::eq(0)) + .returning(move |_| Ok(Some(project.clone()))); + + mock_contexts + .in_use_context_mock + .expect_get_by_id() + .with(predicate::eq(0)) + .returning(move |_| Ok(Some(in_use.clone()))); + + mock_contexts + .query_context_mock + .expect_get_all_by_project_id() + .with(predicate::eq(0)) + .returning(move |_| Ok(queries.clone())); + + let mut request = Request::new(GetProjectRequest { id: 0 }); + + request.metadata_mut().insert("uid", "0".parse().unwrap()); + + let contexts = disguise_context_mocks(mock_contexts); + let project_logic = ProjectController::new(contexts); + + let res = project_logic.get_project(request).await; + + assert!(res.unwrap().get_ref().queries.is_empty()); + } + + #[tokio::test] + async fn get_project_query_has_no_result_query_is_empty() { + let mut mock_contexts = get_mock_contexts(); + + let project = project::Model { + id: Default::default(), + name: "project".to_string(), + components_info: Default::default(), + owner_id: 0, + }; + + let access = access::Model { + id: Default::default(), + role: "Editor".to_string(), + project_id: 1, + user_id: 1, + }; + + let in_use = in_use::Model { + project_id: Default::default(), + session_id: 0, + latest_activity: Utc::now().naive_utc(), + }; + + let query = query::Model { + id: 0, + project_id: 1, + string: "query".to_owned(), + result: None, + outdated: false, + }; + + let queries: Vec = vec![query]; + + mock_contexts + .access_context_mock + .expect_get_access_by_uid_and_project_id() + .with(predicate::eq(0), predicate::eq(0)) + .returning(move |_, _| Ok(Some(access.clone()))); + + mock_contexts + .project_context_mock + .expect_get_by_id() + .with(predicate::eq(0)) + .returning(move |_| Ok(Some(project.clone()))); + + mock_contexts + .in_use_context_mock + .expect_get_by_id() + .with(predicate::eq(0)) + .returning(move |_| Ok(Some(in_use.clone()))); + + mock_contexts + .query_context_mock + .expect_get_all_by_project_id() + .with(predicate::eq(0)) + .returning(move |_| Ok(queries.clone())); + + let mut request = Request::new(GetProjectRequest { id: 0 }); + + request.metadata_mut().insert("uid", "0".parse().unwrap()); + + let contexts = disguise_context_mocks(mock_contexts); + let project_logic = ProjectController::new(contexts); + + let res = project_logic.get_project(request).await; + + assert!(res.unwrap().get_ref().queries[0].result.is_empty()); + } + + #[tokio::test] + async fn list_projects_info_returns_ok() { + let mut mock_contexts = get_mock_contexts(); + + let project_info = ProjectInfo { + project_id: 1, + project_name: "project::Model name".to_owned(), + project_owner_id: 1, + user_role_on_project: "Editor".to_owned(), + }; + + mock_contexts + .project_context_mock + .expect_get_project_info_by_uid() + .with(predicate::eq(1)) + .returning(move |_| Ok(vec![project_info.clone()])); + + let mut list_projects_info_request = Request::new(()); + + list_projects_info_request + .metadata_mut() + .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); + + let contexts = disguise_context_mocks(mock_contexts); + let project_logic = ProjectController::new(contexts); + + let res = project_logic + .list_projects_info(list_projects_info_request) + .await; + + assert!(res.is_ok()); + } + + #[tokio::test] + async fn list_projects_info_returns_err() { + let mut mock_contexts = get_mock_contexts(); + + mock_contexts + .project_context_mock + .expect_get_project_info_by_uid() + .with(predicate::eq(1)) + .returning(move |_| Ok(vec![])); + + let mut list_projects_info_request = Request::new(()); + + list_projects_info_request + .metadata_mut() + .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); + + let contexts = disguise_context_mocks(mock_contexts); + let project_logic = ProjectController::new(contexts); + + let res = project_logic + .list_projects_info(list_projects_info_request) + .await; + + assert!(res.is_err()); + } + + #[tokio::test] + async fn update_name_returns_ok() { + let mut mock_contexts = get_mock_contexts(); + + let user_id = 1; + let project_id = 1; + let new_project_name = "new_name".to_string(); + + let mut update_project_request = Request::new(UpdateProjectRequest { + id: project_id, + name: Some(new_project_name.clone()), + components_info: None, + owner_id: None, + }); + + update_project_request.metadata_mut().insert( + "authorization", + metadata::MetadataValue::from_str("Bearer access_token").unwrap(), + ); + + update_project_request.metadata_mut().insert( + "uid", + metadata::MetadataValue::from_str(user_id.to_string().as_str()).unwrap(), + ); + + mock_contexts + .project_context_mock + .expect_get_by_id() + .with(predicate::eq(project_id)) + .returning(move |_| { + Ok(Some(project::Model { + id: project_id, + name: "old_name".to_owned(), + components_info: Default::default(), + owner_id: user_id, + })) + }); + + mock_contexts + .access_context_mock + .expect_get_access_by_uid_and_project_id() + .with(predicate::eq(1), predicate::eq(project_id)) + .returning(move |_, _| { + Ok(Some(access::Model { + id: 1, + user_id, + project_id, + role: "Editor".to_string(), + })) + }); + + mock_contexts + .session_context_mock + .expect_get_by_token() + .with( + predicate::eq(TokenType::AccessToken), + predicate::eq("access_token".to_string()), + ) + .returning(move |_, _| { + Ok(Some(session::Model { + id: 1, + refresh_token: "refresh_token".to_string(), + access_token: "access_token".to_string(), + updated_at: Default::default(), + user_id, + })) + }); + + mock_contexts + .project_context_mock + .expect_update() + .returning(move |_| { + Ok(project::Model { + id: project_id, + name: new_project_name.clone(), + components_info: Default::default(), + owner_id: user_id, + }) + }); + + mock_contexts + .in_use_context_mock + .expect_get_by_id() + .returning(move |_| { + Ok(Some(in_use::Model { + project_id, + session_id: 1, + latest_activity: Utc::now().naive_utc(), + })) + }); + + mock_contexts + .in_use_context_mock + .expect_update() + .returning(move |_| { + Ok(in_use::Model { + project_id: 1, + session_id: 1, + latest_activity: Utc::now().naive_utc(), + }) + }); + + let contexts = disguise_context_mocks(mock_contexts); + let project_logic = ProjectController::new(contexts); + + let res = project_logic.update_project(update_project_request).await; + + assert!(res.is_ok()); + } + + #[tokio::test] + async fn update_components_info_returns_ok() { + let mut mock_contexts = get_mock_contexts(); + + let user_id = 1; + let project_id = 1; + let components_info_non_json = ComponentsInfo { + components: vec![Component { + rep: Some(Rep::Json("a".to_owned())), + }], + components_hash: 1234456, + }; + let components_info = serde_json::to_value(components_info_non_json.clone()).unwrap(); + + let mut update_project_request = Request::new(UpdateProjectRequest { + id: project_id, + name: None, + components_info: Some(components_info_non_json.clone()), + owner_id: None, + }); + + update_project_request.metadata_mut().insert( + "authorization", + metadata::MetadataValue::from_str("Bearer access_token").unwrap(), + ); + + update_project_request.metadata_mut().insert( + "uid", + metadata::MetadataValue::from_str(user_id.to_string().as_str()).unwrap(), + ); + + mock_contexts + .project_context_mock + .expect_get_by_id() + .with(predicate::eq(project_id)) + .returning(move |_| { + Ok(Some(project::Model { + id: project_id, + name: Default::default(), + components_info: Default::default(), + owner_id: user_id, + })) + }); + + mock_contexts + .access_context_mock + .expect_get_access_by_uid_and_project_id() + .with(predicate::eq(1), predicate::eq(project_id)) + .returning(move |_, _| { + Ok(Some(access::Model { + id: 1, + user_id, + project_id, + role: "Editor".to_string(), + })) + }); + + mock_contexts + .session_context_mock + .expect_get_by_token() + .with( + predicate::eq(TokenType::AccessToken), + predicate::eq("access_token".to_string()), + ) + .returning(move |_, _| { + Ok(Some(session::Model { + id: 1, + refresh_token: "refresh_token".to_string(), + access_token: "access_token".to_string(), + updated_at: Default::default(), + user_id, + })) + }); + + mock_contexts + .project_context_mock + .expect_update() + .returning(move |_| { + Ok(project::Model { + id: project_id, + name: Default::default(), + components_info: components_info.clone(), + owner_id: user_id, + }) + }); + + mock_contexts + .in_use_context_mock + .expect_get_by_id() + .returning(move |_| { + Ok(Some(in_use::Model { + project_id, + session_id: 1, + latest_activity: Utc::now().naive_utc(), + })) + }); + + mock_contexts + .in_use_context_mock + .expect_update() + .returning(move |_| { + Ok(in_use::Model { + project_id: 1, + session_id: 1, + latest_activity: Utc::now().naive_utc(), + }) + }); + + let contexts = disguise_context_mocks(mock_contexts); + let project_logic = ProjectController::new(contexts); + + let res = project_logic.update_project(update_project_request).await; + + assert!(res.is_ok()); + } + + #[tokio::test] + async fn update_owner_id_returns_ok() { + let mut mock_contexts = get_mock_contexts(); + + let user_id = 1; + let project_id = 1; + let new_owner_id = 2; + + let mut update_project_request = Request::new(UpdateProjectRequest { + id: project_id, + name: None, + components_info: None, + owner_id: Some(new_owner_id), + }); + + update_project_request.metadata_mut().insert( + "authorization", + metadata::MetadataValue::from_str("Bearer access_token").unwrap(), + ); + + update_project_request.metadata_mut().insert( + "uid", + metadata::MetadataValue::from_str(user_id.to_string().as_str()).unwrap(), + ); + + mock_contexts + .project_context_mock + .expect_get_by_id() + .with(predicate::eq(project_id)) + .returning(move |_| { + Ok(Some(project::Model { + id: project_id, + name: Default::default(), + components_info: Default::default(), + owner_id: user_id, + })) + }); + + mock_contexts + .access_context_mock + .expect_get_access_by_uid_and_project_id() + .with(predicate::eq(1), predicate::eq(project_id)) + .returning(move |_, _| { + Ok(Some(access::Model { + id: 1, + user_id, + project_id, + role: "Editor".to_string(), + })) + }); + + mock_contexts + .session_context_mock + .expect_get_by_token() + .with( + predicate::eq(TokenType::AccessToken), + predicate::eq("access_token".to_string()), + ) + .returning(move |_, _| { + Ok(Some(session::Model { + id: 1, + refresh_token: "refresh_token".to_string(), + access_token: "access_token".to_string(), + updated_at: Default::default(), + user_id, + })) + }); + + mock_contexts + .project_context_mock + .expect_update() + .returning(move |_| { + Ok(project::Model { + id: project_id, + name: Default::default(), + components_info: Default::default(), + owner_id: new_owner_id, + }) + }); + + mock_contexts + .in_use_context_mock + .expect_get_by_id() + .returning(move |_| { + Ok(Some(in_use::Model { + project_id, + session_id: 1, + latest_activity: Utc::now().naive_utc(), + })) + }); + + mock_contexts + .in_use_context_mock + .expect_update() + .returning(move |_| { + Ok(in_use::Model { + project_id: 1, + session_id: 1, + latest_activity: Utc::now().naive_utc(), + }) + }); + + let contexts = disguise_context_mocks(mock_contexts); + let project_logic = ProjectController::new(contexts); + + let res = project_logic.update_project(update_project_request).await; + + assert!(res.is_ok()); + } + + #[tokio::test] + async fn update_returns_ok() { + let mut mock_contexts = get_mock_contexts(); + + let user_id = 1; + let project_id = 1; + let new_project_name = "new_name".to_string(); + let new_components_info_non_json = ComponentsInfo { + components: vec![Component { + rep: Some(Rep::Json("a".to_owned())), + }], + components_hash: 1234456, + }; + let new_components_info = + serde_json::to_value(new_components_info_non_json.clone()).unwrap(); + let new_owner_id = 2; + + let mut update_project_request = Request::new(UpdateProjectRequest { + id: project_id, + name: Some(new_project_name.clone()), + components_info: Some(new_components_info_non_json.clone()), + owner_id: Some(new_owner_id), + }); + + update_project_request.metadata_mut().insert( + "authorization", + metadata::MetadataValue::from_str("Bearer access_token").unwrap(), + ); + + update_project_request.metadata_mut().insert( + "uid", + metadata::MetadataValue::from_str(user_id.to_string().as_str()).unwrap(), + ); + + mock_contexts + .project_context_mock + .expect_get_by_id() + .with(predicate::eq(project_id)) + .returning(move |_| { + Ok(Some(project::Model { + id: project_id, + name: "old_name".to_owned(), + components_info: serde_json::to_value("{\"old_components\":1}").unwrap(), + owner_id: user_id, + })) + }); + + mock_contexts + .access_context_mock + .expect_get_access_by_uid_and_project_id() + .with(predicate::eq(1), predicate::eq(project_id)) + .returning(move |_, _| { + Ok(Some(access::Model { + id: 1, + user_id, + project_id, + role: "Editor".to_string(), + })) + }); + + mock_contexts + .session_context_mock + .expect_get_by_token() + .with( + predicate::eq(TokenType::AccessToken), + predicate::eq("access_token".to_string()), + ) + .returning(move |_, _| { + Ok(Some(session::Model { + id: 1, + refresh_token: "refresh_token".to_string(), + access_token: "access_token".to_string(), + updated_at: Default::default(), + user_id, + })) + }); + + mock_contexts + .project_context_mock + .expect_update() + .returning(move |_| { + Ok(project::Model { + id: project_id, + name: new_project_name.clone(), + components_info: new_components_info.clone(), + owner_id: new_owner_id, + }) + }); + + mock_contexts + .in_use_context_mock + .expect_get_by_id() + .returning(move |_| { + Ok(Some(in_use::Model { + project_id, + session_id: 1, + latest_activity: Utc::now().naive_utc(), + })) + }); + + mock_contexts + .in_use_context_mock + .expect_update() + .returning(move |_| { + Ok(in_use::Model { + project_id: 1, + session_id: 1, + latest_activity: Utc::now().naive_utc(), + }) + }); + + let contexts = disguise_context_mocks(mock_contexts); + let project_logic = ProjectController::new(contexts); + + let res = project_logic.update_project(update_project_request).await; + + assert!(res.is_ok()); + } + + #[tokio::test] + async fn update_owner_not_owner_returns_err() { + let mut mock_contexts = get_mock_contexts(); + + mock_contexts + .project_context_mock + .expect_get_by_id() + .with(predicate::eq(1)) + .returning(move |_| { + Ok(Some(project::Model { + id: 1, + name: Default::default(), + components_info: Default::default(), + owner_id: 2, + })) + }); + + mock_contexts + .access_context_mock + .expect_get_access_by_uid_and_project_id() + .with(predicate::eq(1), predicate::eq(1)) + .returning(move |_, _| { + Ok(Some(access::Model { + id: 1, + user_id: 1, + project_id: 1, + role: "Editor".to_owned(), + })) + }); + + mock_contexts + .session_context_mock + .expect_get_by_token() + .with( + predicate::eq(TokenType::AccessToken), + predicate::eq("access_token".to_string()), + ) + .returning(move |_, _| { + Ok(Some(session::Model { + id: 1, + refresh_token: "refresh_token".to_string(), + access_token: "access_token".to_string(), + updated_at: Default::default(), + user_id: 1, + })) + }); + + mock_contexts + .in_use_context_mock + .expect_get_by_id() + .with(predicate::eq(1)) + .returning(move |_| { + Ok(Some(in_use::Model { + session_id: 1, + latest_activity: Default::default(), + project_id: 1, + })) + }); + + mock_contexts + .in_use_context_mock + .expect_update() + .returning(move |_| { + Ok(in_use::Model { + session_id: 1, + latest_activity: Default::default(), + project_id: 1, + }) + }); + + let mut request = Request::new(UpdateProjectRequest { + id: 1, + name: None, + components_info: None, + owner_id: Some(1), + }); + + request + .metadata_mut() + .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); + + request.metadata_mut().insert( + "authorization", + metadata::MetadataValue::from_str("access_token").unwrap(), + ); + + let contexts = disguise_context_mocks(mock_contexts); + let project_logic = ProjectController::new(contexts); + + let res = project_logic.update_project(request).await.unwrap_err(); + + assert_eq!(res.code(), Code::PermissionDenied); + } + + #[tokio::test] + async fn update_no_in_use_returns_err() { + let mut mock_contexts = get_mock_contexts(); + + mock_contexts + .project_context_mock + .expect_get_by_id() + .with(predicate::eq(1)) + .returning(move |_| { + Ok(Some(project::Model { + id: 1, + name: Default::default(), + components_info: Default::default(), + owner_id: 1, + })) + }); + + mock_contexts + .access_context_mock + .expect_get_access_by_uid_and_project_id() + .with(predicate::eq(1), predicate::eq(1)) + .returning(move |_, _| { + Ok(Some(access::Model { + id: 1, + user_id: 1, + project_id: 1, + role: "Editor".to_owned(), + })) + }); + + mock_contexts + .session_context_mock + .expect_get_by_token() + .with( + predicate::eq(TokenType::AccessToken), + predicate::eq("access_token".to_string()), + ) + .returning(move |_, _| { + Ok(Some(session::Model { + id: 1, + refresh_token: "refresh_token".to_string(), + access_token: "access_token".to_string(), + updated_at: Default::default(), + user_id: 1, + })) + }); + + mock_contexts + .in_use_context_mock + .expect_get_by_id() + .with(predicate::eq(1)) + .returning(move |_| { + Ok(Some(in_use::Model { + session_id: 2, + latest_activity: Utc::now().naive_utc(), + project_id: 1, + })) + }); + + let mut request = Request::new(UpdateProjectRequest { + id: 1, + name: None, + components_info: None, + owner_id: None, + }); + + request + .metadata_mut() + .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); + + request.metadata_mut().insert( + "authorization", + metadata::MetadataValue::from_str("access_token").unwrap(), + ); + + let contexts = disguise_context_mocks(mock_contexts); + let project_logic = ProjectController::new(contexts); + + let res = project_logic.update_project(request).await.unwrap_err(); + + assert_eq!(res.code(), Code::FailedPrecondition); + } + + #[tokio::test] + async fn update_no_access_returns_err() { + let mut mock_contexts = get_mock_contexts(); + + mock_contexts + .project_context_mock + .expect_get_by_id() + .with(predicate::eq(1)) + .returning(move |_| { + Ok(Some(project::Model { + id: 1, + name: Default::default(), + components_info: Default::default(), + owner_id: 1, + })) + }); + + mock_contexts + .access_context_mock + .expect_get_access_by_uid_and_project_id() + .with(predicate::eq(1), predicate::eq(1)) + .returning(move |_, _| Ok(None)); + + let mut request = Request::new(UpdateProjectRequest { + id: 1, + name: None, + components_info: None, + owner_id: None, + }); + + request + .metadata_mut() + .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); + + let contexts = disguise_context_mocks(mock_contexts); + let project_logic = ProjectController::new(contexts); + + let res = project_logic.update_project(request).await.unwrap_err(); + + assert_eq!(res.code(), Code::PermissionDenied); + } + + #[tokio::test] + async fn update_incorrect_role_returns_err() { + let mut mock_contexts = get_mock_contexts(); + + mock_contexts + .project_context_mock + .expect_get_by_id() + .with(predicate::eq(1)) + .returning(move |_| { + Ok(Some(project::Model { + id: 1, + name: Default::default(), + components_info: Default::default(), + owner_id: 1, + })) + }); + + mock_contexts + .access_context_mock + .expect_get_access_by_uid_and_project_id() + .with(predicate::eq(1), predicate::eq(1)) + .returning(move |_, _| { + Ok(Some(access::Model { + id: 1, + user_id: 1, + project_id: 1, + role: "Viewer".to_owned(), + })) + }); + + let mut request = Request::new(UpdateProjectRequest { + id: 1, + name: None, + components_info: None, + owner_id: None, + }); + + request + .metadata_mut() + .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); + + let contexts = disguise_context_mocks(mock_contexts); + let project_logic = ProjectController::new(contexts); + + let res = project_logic.update_project(request).await.unwrap_err(); + + assert_eq!(res.code(), Code::PermissionDenied); + } + + #[tokio::test] + async fn update_no_session_returns_err() { + let mut mock_contexts = get_mock_contexts(); + + mock_contexts + .project_context_mock + .expect_get_by_id() + .with(predicate::eq(1)) + .returning(move |_| { + Ok(Some(project::Model { + id: 1, + name: Default::default(), + components_info: Default::default(), + owner_id: 1, + })) + }); + + mock_contexts + .access_context_mock + .expect_get_access_by_uid_and_project_id() + .with(predicate::eq(1), predicate::eq(1)) + .returning(move |_, _| { + Ok(Some(access::Model { + id: 1, + user_id: 1, + project_id: 1, + role: "Editor".to_owned(), + })) + }); + + mock_contexts + .session_context_mock + .expect_get_by_token() + .with( + predicate::eq(TokenType::AccessToken), + predicate::eq("access_token".to_string()), + ) + .returning(move |_, _| Ok(None)); + + let mut request = Request::new(UpdateProjectRequest { + id: 1, + name: None, + components_info: None, + owner_id: None, + }); + + request + .metadata_mut() + .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); + + request.metadata_mut().insert( + "authorization", + metadata::MetadataValue::from_str("access_token").unwrap(), + ); + + let contexts = disguise_context_mocks(mock_contexts); + let project_logic = ProjectController::new(contexts); + + let res = project_logic.update_project(request).await.unwrap_err(); + + assert_eq!(res.code(), Code::Unauthenticated); + } + + #[tokio::test] + async fn update_no_project_returns_err() { + let mut mock_contexts = get_mock_contexts(); + + mock_contexts + .project_context_mock + .expect_get_by_id() + .with(predicate::eq(2)) + .returning(move |_| Ok(None)); + + let mut request = Request::new(UpdateProjectRequest { + id: 2, + name: None, + components_info: None, + owner_id: None, + }); + + request + .metadata_mut() + .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); + + let contexts = disguise_context_mocks(mock_contexts); + let project_logic = ProjectController::new(contexts); + + let res = project_logic.update_project(request).await.unwrap_err(); + + assert_eq!(res.code(), Code::NotFound); + } +} diff --git a/src/controllers/controller_impls/query_controller.rs b/src/controllers/controller_impls/query_controller.rs index 8244146..a1b96d8 100644 --- a/src/controllers/controller_impls/query_controller.rs +++ b/src/controllers/controller_impls/query_controller.rs @@ -303,5 +303,600 @@ impl QueryControllerTrait for QueryController { } #[cfg(test)] -#[path = "../../tests/controllers/query_controller.rs"] -mod query_controller_tests; +mod tests { + use super::super::helpers::{ + disguise_context_mocks, disguise_service_mocks, get_mock_contexts, get_mock_services, + }; + use crate::api::server::protobuf::query_response::{self, Result}; + use crate::api::server::protobuf::{ + CreateQueryRequest, DeleteQueryRequest, QueryResponse, SendQueryRequest, UpdateQueryRequest, + }; + use crate::controllers::controller_impls::QueryController; + use crate::controllers::controller_traits::QueryControllerTrait; + use crate::entities::{access, project, query}; + use mockall::predicate; + use sea_orm::DbErr; + use std::str::FromStr; + use tonic::{metadata, Code, Request, Response}; + + #[tokio::test] + async fn create_invalid_query_returns_err() { + let mut mock_contexts = get_mock_contexts(); + let mock_services = get_mock_services(); + + let query = query::Model { + id: Default::default(), + string: "".to_string(), + result: Default::default(), + project_id: 1, + outdated: Default::default(), + }; + + let access = access::Model { + id: Default::default(), + role: "Editor".to_string(), + project_id: 1, + user_id: 1, + }; + + mock_contexts + .access_context_mock + .expect_get_access_by_uid_and_project_id() + .with(predicate::eq(1), predicate::eq(1)) + .returning(move |_, _| Ok(Some(access.clone()))); + + mock_contexts + .query_context_mock + .expect_create() + .with(predicate::eq(query.clone())) + .returning(move |_| Err(DbErr::RecordNotInserted)); + + let mut request = Request::new(CreateQueryRequest { + string: "".to_string(), + project_id: 1, + }); + + request + .metadata_mut() + .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); + + let contexts = disguise_context_mocks(mock_contexts); + let services = disguise_service_mocks(mock_services); + let query_logic = QueryController::new(contexts, services); + + let res = query_logic.create_query(request).await.unwrap_err(); + + assert_eq!(res.code(), Code::Internal); + } + + #[tokio::test] + async fn create_query_returns_ok() { + let mut mock_contexts = get_mock_contexts(); + let mock_services = get_mock_services(); + + let query = query::Model { + id: Default::default(), + string: "".to_string(), + result: Default::default(), + project_id: 1, + outdated: Default::default(), + }; + + let access = access::Model { + id: Default::default(), + role: "Editor".to_string(), + project_id: 1, + user_id: 1, + }; + + mock_contexts + .access_context_mock + .expect_get_access_by_uid_and_project_id() + .with(predicate::eq(1), predicate::eq(1)) + .returning(move |_, _| Ok(Some(access.clone()))); + + mock_contexts + .query_context_mock + .expect_create() + .with(predicate::eq(query.clone())) + .returning(move |_| Ok(query.clone())); + + let mut request = Request::new(CreateQueryRequest { + string: "".to_string(), + project_id: 1, + }); + + request + .metadata_mut() + .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); + + let contexts = disguise_context_mocks(mock_contexts); + let services = disguise_service_mocks(mock_services); + let query_logic = QueryController::new(contexts, services); + + let res = query_logic.create_query(request).await; + + assert!(res.is_ok()); + } + + #[tokio::test] + async fn update_invalid_query_returns_err() { + let mut mock_contexts = get_mock_contexts(); + let mock_services = get_mock_services(); + + let old_query = query::Model { + id: 1, + string: "".to_string(), + result: None, + project_id: Default::default(), + outdated: true, + }; + + let query = query::Model { + string: "updated".to_string(), + ..old_query.clone() + }; + + let access = access::Model { + id: 1, + role: "Editor".to_string(), + project_id: Default::default(), + user_id: 1, + }; + + mock_contexts + .query_context_mock + .expect_get_by_id() + .with(predicate::eq(1)) + .returning(move |_| Ok(Some(old_query.clone()))); + + mock_contexts + .access_context_mock + .expect_get_access_by_uid_and_project_id() + .with(predicate::eq(1), predicate::eq(0)) + .returning(move |_, _| Ok(Some(access.clone()))); + + mock_contexts + .query_context_mock + .expect_update() + .with(predicate::eq(query.clone())) + .returning(move |_| Err(DbErr::RecordNotUpdated)); + + let mut request = Request::new(UpdateQueryRequest { + id: 1, + string: "updated".to_string(), + }); + + request + .metadata_mut() + .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); + + let contexts = disguise_context_mocks(mock_contexts); + let services = disguise_service_mocks(mock_services); + let query_logic = QueryController::new(contexts, services); + + let res = query_logic.update_query(request).await.unwrap_err(); + + assert_eq!(res.code(), Code::Internal); + } + + #[tokio::test] + async fn update_query_returns_ok() { + let mut mock_contexts = get_mock_contexts(); + let mock_services = get_mock_services(); + + let old_query = query::Model { + id: 1, + string: "".to_string(), + result: None, + project_id: Default::default(), + outdated: true, + }; + + let query = query::Model { + string: "updated".to_string(), + ..old_query.clone() + }; + + let access = access::Model { + id: Default::default(), + role: "Editor".to_string(), + project_id: Default::default(), + user_id: 1, + }; + + mock_contexts + .access_context_mock + .expect_get_access_by_uid_and_project_id() + .with(predicate::eq(1), predicate::eq(0)) + .returning(move |_, _| Ok(Some(access.clone()))); + + mock_contexts + .query_context_mock + .expect_get_by_id() + .with(predicate::eq(1)) + .returning(move |_| Ok(Some(old_query.clone()))); + + mock_contexts + .query_context_mock + .expect_update() + .with(predicate::eq(query.clone())) + .returning(move |_| Ok(query.clone())); + + let mut request = Request::new(UpdateQueryRequest { + id: 1, + string: "updated".to_string(), + }); + + request + .metadata_mut() + .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); + + let contexts = disguise_context_mocks(mock_contexts); + let services = disguise_service_mocks(mock_services); + let query_logic = QueryController::new(contexts, services); + + let res = query_logic.update_query(request).await; + + assert!(res.is_ok()); + } + + #[tokio::test] + async fn delete_invalid_query_returns_err() { + let mut mock_contexts = get_mock_contexts(); + let mock_services = get_mock_services(); + + let access = access::Model { + id: Default::default(), + role: "Editor".to_string(), + project_id: Default::default(), + user_id: 1, + }; + + let query = query::Model { + id: 1, + string: "".to_string(), + result: Default::default(), + project_id: Default::default(), + outdated: Default::default(), + }; + + mock_contexts + .access_context_mock + .expect_get_access_by_uid_and_project_id() + .with(predicate::eq(1), predicate::eq(0)) + .returning(move |_, _| Ok(Some(access.clone()))); + + mock_contexts + .query_context_mock + .expect_get_by_id() + .with(predicate::eq(1)) + .returning(move |_| Ok(Some(query.clone()))); + + mock_contexts + .query_context_mock + .expect_delete() + .with(predicate::eq(1)) + .returning(move |_| Err(DbErr::RecordNotFound("".to_string()))); + + let mut request = Request::new(DeleteQueryRequest { id: 1 }); + + request + .metadata_mut() + .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); + + let contexts = disguise_context_mocks(mock_contexts); + let services = disguise_service_mocks(mock_services); + let query_logic = QueryController::new(contexts, services); + + let res = query_logic.delete_query(request).await.unwrap_err(); + + assert_eq!(res.code(), Code::NotFound); + } + + #[tokio::test] + async fn delete_query_returns_ok() { + let mut mock_contexts = get_mock_contexts(); + let mock_services = get_mock_services(); + + let query = query::Model { + id: 1, + string: "".to_string(), + result: Default::default(), + project_id: Default::default(), + outdated: Default::default(), + }; + + let query_clone = query.clone(); + + let access = access::Model { + id: Default::default(), + role: "Editor".to_string(), + project_id: Default::default(), + user_id: 1, + }; + + mock_contexts + .query_context_mock + .expect_get_by_id() + .with(predicate::eq(1)) + .returning(move |_| Ok(Some(query.clone()))); + + mock_contexts + .access_context_mock + .expect_get_access_by_uid_and_project_id() + .with(predicate::eq(1), predicate::eq(0)) + .returning(move |_, _| Ok(Some(access.clone()))); + + mock_contexts + .query_context_mock + .expect_delete() + .with(predicate::eq(1)) + .returning(move |_| Ok(query_clone.clone())); + + let mut request = Request::new(DeleteQueryRequest { id: 1 }); + + request + .metadata_mut() + .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); + + let contexts = disguise_context_mocks(mock_contexts); + let services = disguise_service_mocks(mock_services); + let query_logic = QueryController::new(contexts, services); + + let res = query_logic.delete_query(request).await; + + assert!(res.is_ok()); + } + + #[tokio::test] + async fn create_query_invalid_role_returns_err() { + let mut mock_contexts = get_mock_contexts(); + let mock_services = get_mock_services(); + + let query = query::Model { + id: 1, + string: "".to_string(), + result: Default::default(), + project_id: Default::default(), + outdated: Default::default(), + }; + + let access = access::Model { + id: Default::default(), + role: "Viewer".to_string(), + project_id: Default::default(), + user_id: 1, + }; + + mock_contexts + .access_context_mock + .expect_get_access_by_uid_and_project_id() + .with(predicate::eq(1), predicate::eq(1)) + .returning(move |_, _| Ok(Some(access.clone()))); + + mock_contexts + .query_context_mock + .expect_create() + .with(predicate::eq(query.clone())) + .returning(move |_| Ok(query.clone())); + + let mut request = Request::new(CreateQueryRequest { + string: "".to_string(), + project_id: 1, + }); + + request + .metadata_mut() + .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); + + let contexts = disguise_context_mocks(mock_contexts); + let services = disguise_service_mocks(mock_services); + let query_logic = QueryController::new(contexts, services); + + let res = query_logic.create_query(request).await.unwrap_err(); + + assert_eq!(res.code(), Code::PermissionDenied); + } + + #[tokio::test] + async fn delete_query_invalid_role_returns_err() { + let mut mock_contexts = get_mock_contexts(); + let mock_services = get_mock_services(); + + let query = query::Model { + id: 1, + string: "".to_string(), + result: Default::default(), + project_id: Default::default(), + outdated: Default::default(), + }; + + let query_clone = query.clone(); + + let access = access::Model { + id: Default::default(), + role: "Viewer".to_string(), + project_id: Default::default(), + user_id: 1, + }; + + mock_contexts + .query_context_mock + .expect_get_by_id() + .with(predicate::eq(1)) + .returning(move |_| Ok(Some(query.clone()))); + + mock_contexts + .access_context_mock + .expect_get_access_by_uid_and_project_id() + .with(predicate::eq(1), predicate::eq(0)) + .returning(move |_, _| Ok(Some(access.clone()))); + + mock_contexts + .query_context_mock + .expect_delete() + .with(predicate::eq(1)) + .returning(move |_| Ok(query_clone.clone())); + + let mut request = Request::new(DeleteQueryRequest { id: 1 }); + + request + .metadata_mut() + .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); + + let contexts = disguise_context_mocks(mock_contexts); + let services = disguise_service_mocks(mock_services); + let query_logic = QueryController::new(contexts, services); + + let res = query_logic.delete_query(request).await.unwrap_err(); + + assert_eq!(res.code(), Code::PermissionDenied); + } + + #[tokio::test] + async fn update_query_invalid_role_returns_err() { + let mut mock_contexts = get_mock_contexts(); + let mock_services = get_mock_services(); + + let old_query = query::Model { + id: 1, + string: "".to_string(), + result: None, + project_id: Default::default(), + outdated: true, + }; + + let query = query::Model { + string: "updated".to_string(), + ..old_query.clone() + }; + + let access = access::Model { + id: Default::default(), + role: "Viewer".to_string(), + project_id: Default::default(), + user_id: 1, + }; + + mock_contexts + .access_context_mock + .expect_get_access_by_uid_and_project_id() + .with(predicate::eq(1), predicate::eq(0)) + .returning(move |_, _| Ok(Some(access.clone()))); + + mock_contexts + .query_context_mock + .expect_get_by_id() + .with(predicate::eq(1)) + .returning(move |_| Ok(Some(old_query.clone()))); + + mock_contexts + .query_context_mock + .expect_update() + .with(predicate::eq(query.clone())) + .returning(move |_| Ok(query.clone())); + + let mut request = Request::new(UpdateQueryRequest { + id: 1, + string: "updated".to_string(), + }); + + request + .metadata_mut() + .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); + + let contexts = disguise_context_mocks(mock_contexts); + let services = disguise_service_mocks(mock_services); + let query_logic = QueryController::new(contexts, services); + + let res = query_logic.update_query(request).await.unwrap_err(); + + assert_eq!(res.code(), Code::PermissionDenied); + } + + #[tokio::test] + async fn send_query_returns_ok() { + let mut mock_contexts = get_mock_contexts(); + let mut mock_services = get_mock_services(); + + let query = query::Model { + id: Default::default(), + string: "".to_string(), + result: Default::default(), + project_id: Default::default(), + outdated: Default::default(), + }; + + let access = access::Model { + id: Default::default(), + role: "Editor".to_string(), + project_id: Default::default(), + user_id: 1, + }; + + let project = project::Model { + id: Default::default(), + name: "project".to_string(), + components_info: Default::default(), + owner_id: 0, + }; + + let query_response = QueryResponse { + query_id: Default::default(), + info: Default::default(), + result: Some(Result::Success(query_response::Success {})), + }; + + let updated_query = query::Model { + result: Some(serde_json::to_value(query_response.clone().result).unwrap()), + ..query.clone() + }; + + mock_contexts + .project_context_mock + .expect_get_by_id() + .with(predicate::eq(0)) + .returning(move |_| Ok(Some(project.clone()))); + + mock_contexts + .access_context_mock + .expect_get_access_by_uid_and_project_id() + .with(predicate::eq(1), predicate::eq(0)) + .returning(move |_, _| Ok(Some(access.clone()))); + + mock_contexts + .query_context_mock + .expect_get_by_id() + .with(predicate::eq(0)) + .returning(move |_| Ok(Some(query.clone()))); + + mock_services + .reveaal_service_mock + .expect_send_query() + .returning(move |_| Ok(Response::new(query_response.clone()))); + + mock_contexts + .query_context_mock + .expect_update() + .with(predicate::eq(updated_query.clone())) + .returning(move |_| Ok(updated_query.clone())); + + let mut request = Request::new(SendQueryRequest { + id: Default::default(), + project_id: Default::default(), + }); + + request + .metadata_mut() + .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); + + let contexts = disguise_context_mocks(mock_contexts); + let services = disguise_service_mocks(mock_services); + let query_logic = QueryController::new(contexts, services); + + let res = query_logic.send_query(request).await; + + assert!(res.is_ok()); + } +} diff --git a/src/controllers/controller_impls/session_controller.rs b/src/controllers/controller_impls/session_controller.rs index a6b6b06..8f8027c 100644 --- a/src/controllers/controller_impls/session_controller.rs +++ b/src/controllers/controller_impls/session_controller.rs @@ -193,5 +193,343 @@ impl SessionControllerTrait for SessionController { } #[cfg(test)] -#[path = "../../tests/controllers/session_controller.rs"] -mod session_controller_tests; +mod tests { + use mockall::predicate; + use std::env; + use std::str::FromStr; + + use super::super::helpers::{ + disguise_context_mocks, disguise_service_mocks, get_mock_contexts, get_mock_services, + }; + use crate::entities::{session, user}; + + use crate::api::auth::{Token, TokenType}; + use crate::api::server::protobuf::get_auth_token_request::{user_credentials, UserCredentials}; + use crate::api::server::protobuf::GetAuthTokenRequest; + use crate::controllers::controller_impls::SessionController; + use crate::controllers::controller_traits::SessionControllerTrait; + use sea_orm::DbErr; + use tonic::{metadata, Code, Request}; + + #[tokio::test] + async fn update_session_no_session_exists_creates_session_returns_err() { + let mut mock_contexts = get_mock_contexts(); + let mock_services = get_mock_services(); + + mock_contexts + .session_context_mock + .expect_get_by_token() + .returning(move |_, _| Ok(None)); + + mock_contexts + .session_context_mock + .expect_update() + .returning(move |_| Err(DbErr::RecordNotInserted)); + + let contexts = disguise_context_mocks(mock_contexts); + let services = disguise_service_mocks(mock_services); + let session_logic = SessionController::new(contexts, services); + + let res = session_logic + .update_session("old_refresh_token".to_string()) + .await; + + assert_eq!(res.unwrap_err().code(), Code::Unauthenticated); + } + + #[tokio::test] + async fn update_session_returns_new_tokens_when_session_exists() { + let mut mock_contexts = get_mock_contexts(); + let mock_services = get_mock_services(); + + let refresh_token = "refresh_token".to_string(); + + mock_contexts + .session_context_mock + .expect_get_by_token() + .times(1) + .returning(|_, _| { + Ok(Some(session::Model { + id: 0, + access_token: "old_access_token".to_string(), + refresh_token: "old_refresh_token".to_string(), + updated_at: Default::default(), + user_id: 1, + })) + }); + + mock_contexts + .session_context_mock + .expect_update() + .times(1) + .returning(move |_| { + Ok(session::Model { + id: 0, + refresh_token: "refresh_token".to_string(), + access_token: "access_token".to_string(), + updated_at: Default::default(), + user_id: 1, + }) + }); + + let contexts = disguise_context_mocks(mock_contexts); + let services = disguise_service_mocks(mock_services); + let session_logic = SessionController::new(contexts, services); + + let result = session_logic.update_session(refresh_token).await; + + assert!(result.is_ok()); + let (access_token, refresh_token) = result.unwrap(); + assert_ne!(access_token.to_string(), "old_access_token"); + assert_ne!(refresh_token.to_string(), "old_refresh_token"); + } + + #[tokio::test] + async fn update_session_returns_error_when_no_session_found() { + let mut mock_contexts = get_mock_contexts(); + let mock_services = get_mock_services(); + + let refresh_token = "refresh_token".to_string(); + + mock_contexts + .session_context_mock + .expect_get_by_token() + .times(1) + .returning(|_, _| Ok(None)); + + let contexts = disguise_context_mocks(mock_contexts); + let services = disguise_service_mocks(mock_services); + let session_logic = SessionController::new(contexts, services); + + let result = session_logic.update_session(refresh_token).await; + + assert!(result.is_err()); + assert_eq!(result.unwrap_err().code(), Code::Unauthenticated); + } + + #[tokio::test] + async fn update_session_returns_error_when_database_error_occurs() { + let mut mock_contexts = get_mock_contexts(); + let mock_services = get_mock_services(); + + let refresh_token = "refresh_token".to_string(); + + mock_contexts + .session_context_mock + .expect_get_by_token() + .times(1) + .returning(|_, _| Err(DbErr::RecordNotFound("".to_string()))); + + let contexts = disguise_context_mocks(mock_contexts); + let services = disguise_service_mocks(mock_services); + let session_logic = SessionController::new(contexts, services); + + let result = session_logic.update_session(refresh_token).await; + + assert!(result.is_err()); + assert_eq!(result.unwrap_err().code(), Code::Internal); + } + + #[tokio::test] + async fn get_auth_token_from_credentials_returns_ok() { + let mut mock_contexts = get_mock_contexts(); + let mut mock_services = get_mock_services(); + + let request = GetAuthTokenRequest { + user_credentials: Option::from(UserCredentials { + password: "Password123".to_string(), + user: Option::from(user_credentials::User::Username("Example".to_string())), + }), + }; + + mock_contexts + .user_context_mock + .expect_get_by_username() + .returning(move |_| { + Ok(Option::from(user::Model { + id: 1, + email: "".to_string(), + username: "Example".to_string(), + password: "".to_string(), + })) + }); + + mock_services + .hashing_service_mock + .expect_verify_password() + .returning(move |_, _| Ok(true)); + + mock_contexts + .session_context_mock + .expect_create() + .returning(move |_| { + Ok(session::Model { + id: 0, + refresh_token: "refresh_token".to_string(), + access_token: "access_token".to_string(), + updated_at: Default::default(), + user_id: 1, + }) + }); + + let contexts = disguise_context_mocks(mock_contexts); + let services = disguise_service_mocks(mock_services); + let session_logic = SessionController::new(contexts, services); + + let response = session_logic + .get_auth_token(Request::new(request)) + .await + .unwrap(); + + assert!(!response.get_ref().refresh_token.is_empty()); + assert!(!response.get_ref().access_token.is_empty()); + } + + #[tokio::test] + async fn get_auth_token_from_token_returns_ok() { + env::set_var("REFRESH_TOKEN_HS512_SECRET", "refresh_secret"); + + let mut mock_contexts = get_mock_contexts(); + let mock_services = get_mock_services(); + + let mut request = Request::new(GetAuthTokenRequest { + user_credentials: None, + }); + + let refresh_token = Token::new(TokenType::RefreshToken, "1").unwrap(); + + request.metadata_mut().insert( + "authorization", + metadata::MetadataValue::from_str(format!("Bearer {}", refresh_token).as_str()) + .unwrap(), + ); + + mock_contexts + .session_context_mock + .expect_get_by_token() + .returning(move |_, _| { + Ok(Option::from(session::Model { + id: 0, + refresh_token: "refresh_token".to_string(), + access_token: "access_token".to_string(), + updated_at: Default::default(), + user_id: 1, + })) + }); + + mock_contexts + .session_context_mock + .expect_update() + .returning(move |_| { + Ok(session::Model { + id: 0, + refresh_token: "refresh_token".to_string(), + access_token: "access_token".to_string(), + updated_at: Default::default(), + user_id: 1, + }) + }); + + let contexts = disguise_context_mocks(mock_contexts); + let services = disguise_service_mocks(mock_services); + let session_logic = SessionController::new(contexts, services); + + let response = session_logic.get_auth_token(request).await.unwrap(); + + assert!(!response.get_ref().refresh_token.is_empty()); + assert!(!response.get_ref().access_token.is_empty()); + } + + #[tokio::test] + async fn get_auth_token_from_invalid_token_returns_err() { + let mock_contexts = get_mock_contexts(); + let mock_services = get_mock_services(); + + let mut request = Request::new(GetAuthTokenRequest { + user_credentials: None, + }); + + request.metadata_mut().insert( + "authorization", + metadata::MetadataValue::from_str("invalid token").unwrap(), + ); + + let contexts = disguise_context_mocks(mock_contexts); + let services = disguise_service_mocks(mock_services); + let session_logic = SessionController::new(contexts, services); + + let response = session_logic.get_auth_token(request).await; + + assert_eq!(response.unwrap_err().code(), Code::Unauthenticated); + } + + #[tokio::test] + async fn delete_session_returns_ok() { + let mut mock_contexts = get_mock_contexts(); + let mock_services = get_mock_services(); + + mock_contexts + .session_context_mock + .expect_delete_by_token() + .with( + predicate::eq(TokenType::AccessToken), + predicate::eq("test_token".to_string()), + ) + .returning(move |_, _| { + Ok(session::Model { + id: 1, + refresh_token: Default::default(), + access_token: "test_token".to_string(), + updated_at: Default::default(), + user_id: Default::default(), + }) + }); + + let contexts = disguise_context_mocks(mock_contexts); + let services = disguise_service_mocks(mock_services); + let session_logic = SessionController::new(contexts, services); + + let mut request = Request::new(()); + request.metadata_mut().insert( + "authorization", + metadata::MetadataValue::from_str("Bearer test_token").unwrap(), + ); + + let res = session_logic.delete_session(request).await; + + assert!(res.is_ok()); + } + + #[tokio::test] + async fn delete_session_no_session_returns_err() { + let mut mock_contexts = get_mock_contexts(); + let mock_services = get_mock_services(); + + mock_contexts + .session_context_mock + .expect_delete_by_token() + .with( + predicate::eq(TokenType::AccessToken), + predicate::eq("test_token".to_string()), + ) + .returning(move |_, _| { + Err(DbErr::RecordNotFound( + "No session found with the provided access token".to_string(), + )) + }); + + let contexts = disguise_context_mocks(mock_contexts); + let services = disguise_service_mocks(mock_services); + let session_logic = SessionController::new(contexts, services); + + let mut request = Request::new(()); + request.metadata_mut().insert( + "authorization", + metadata::MetadataValue::from_str("Bearer test_token").unwrap(), + ); + + let res = session_logic.delete_session(request).await; + + assert_eq!(res.unwrap_err().code(), Code::Internal); + } +} diff --git a/src/controllers/controller_impls/user_controller.rs b/src/controllers/controller_impls/user_controller.rs index b406553..e8c2890 100644 --- a/src/controllers/controller_impls/user_controller.rs +++ b/src/controllers/controller_impls/user_controller.rs @@ -203,5 +203,417 @@ impl UserControllerTrait for UserController { } #[cfg(test)] -#[path = "../../tests/controllers/user_controller.rs"] -mod user_controller_tests; +mod tests { + use super::super::helpers::{ + disguise_context_mocks, disguise_service_mocks, get_mock_contexts, get_mock_services, + }; + use crate::api::server::protobuf::{CreateUserRequest, GetUsersRequest, UpdateUserRequest}; + use crate::controllers::controller_impls::UserController; + use crate::controllers::controller_traits::UserControllerTrait; + use crate::entities::user; + use mockall::predicate; + use sea_orm::DbErr; + use std::str::FromStr; + use tonic::{metadata, Code, Request}; + + #[tokio::test] + async fn delete_user_nonexistent_user_returns_err() { + let mut mock_contexts = get_mock_contexts(); + let mock_services = get_mock_services(); + + mock_contexts + .user_context_mock + .expect_delete() + .with(predicate::eq(1)) + .returning(|_| Err(DbErr::RecordNotFound("".into()))); + + let contexts = disguise_context_mocks(mock_contexts); + let services = disguise_service_mocks(mock_services); + let user_logic = UserController::new(contexts, services); + + let mut delete_request = Request::new(()); + + // Insert uid into request metadata + delete_request + .metadata_mut() + .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); + + let delete_response = user_logic.delete_user(delete_request).await.unwrap_err(); + let expected_response_code = Code::Internal; + + assert_eq!(delete_response.code(), expected_response_code); + } + + #[tokio::test] + async fn delete_user_existing_user_returns_ok() { + let mut mock_contexts = get_mock_contexts(); + let mock_services = get_mock_services(); + + let user = user::Model { + id: 1, + email: "".to_string(), + username: "".to_string(), + password: "".to_string(), + }; + + mock_contexts + .user_context_mock + .expect_delete() + .with(predicate::eq(1)) + .returning(move |_| Ok(user.clone())); + + let contexts = disguise_context_mocks(mock_contexts); + let services = disguise_service_mocks(mock_services); + let user_logic = UserController::new(contexts, services); + + let mut delete_request = Request::new(()); + + // Insert uid into request metadata + delete_request + .metadata_mut() + .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); + + let delete_response = user_logic.delete_user(delete_request).await; + + assert!(delete_response.is_ok()); + } + + #[tokio::test] + async fn create_user_nonexistent_user_returns_ok() { + let mut mock_contexts = get_mock_contexts(); + let mut mock_services = get_mock_services(); + + let password = "Password123".to_string(); + + let user = user::Model { + id: Default::default(), + email: "anders21@student.aau.dk".to_string(), + username: "anders".to_string(), + password: password.clone(), + }; + + let create_user_request = Request::new(CreateUserRequest { + email: "anders21@student.aau.dk".to_string(), + username: "anders".to_string(), + password: password.clone(), + }); + + mock_services + .hashing_service_mock + .expect_hash_password() + .returning(move |_| Ok(password.clone())); + + mock_contexts + .user_context_mock + .expect_create() + .with(predicate::eq(user.clone())) + .returning(move |_| Ok(user.clone())); + + let contexts = disguise_context_mocks(mock_contexts); + let services = disguise_service_mocks(mock_services); + let user_logic = UserController::new(contexts, services); + + let create_user_response = user_logic.create_user(create_user_request).await; + assert!(create_user_response.is_ok()); + } + + #[tokio::test] + async fn create_user_duplicate_email_returns_error() { + let mut mock_contexts = get_mock_contexts(); + let mut mock_services = get_mock_services(); + + let password = "Password123".to_string(); + + let user = user::Model { + id: Default::default(), + email: "anders21@student.aau.dk".to_string(), + username: "anders".to_string(), + password: password.clone(), + }; + + let create_user_request = Request::new(CreateUserRequest { + email: "anders21@student.aau.dk".to_string(), + username: "anders".to_string(), + password: password.clone(), + }); + + mock_services + .hashing_service_mock + .expect_hash_password() + .returning(move |_| Ok(password.clone())); + + mock_contexts + .user_context_mock + .expect_create() + .with(predicate::eq(user.clone())) + .returning(move |_| Err(DbErr::RecordNotInserted)); //todo!("Needs to be a SqlError with UniqueConstraintViolation with 'email' in message) + + let contexts = disguise_context_mocks(mock_contexts); + let services = disguise_service_mocks(mock_services); + let user_logic = UserController::new(contexts, services); + + let res = user_logic.create_user(create_user_request).await; + assert_eq!(res.unwrap_err().code(), Code::Internal); //todo!("Needs to be code AlreadyExists when mocked Error is corrected) + } + + #[tokio::test] + async fn create_user_invalid_email_returns_error() { + let mock_contexts = get_mock_contexts(); + let mock_services = get_mock_services(); + + let contexts = disguise_context_mocks(mock_contexts); + let services = disguise_service_mocks(mock_services); + let user_logic = UserController::new(contexts, services); + + let create_user_request = Request::new(CreateUserRequest { + email: "invalid-email".to_string(), + username: "newuser".to_string(), + password: "123".to_string(), + }); + + let res = user_logic.create_user(create_user_request).await; + assert_eq!(res.unwrap_err().code(), Code::InvalidArgument); + } + + #[tokio::test] + async fn create_user_duplicate_username_returns_error() { + let mut mock_contexts = get_mock_contexts(); + let mut mock_services = get_mock_services(); + + let password = "Password123".to_string(); + + let user = user::Model { + id: Default::default(), + email: "anders21@student.aau.dk".to_string(), + username: "anders".to_string(), + password: password.clone(), + }; + + let create_user_request = Request::new(CreateUserRequest { + email: "anders21@student.aau.dk".to_string(), + username: "anders".to_string(), + password: password.clone(), + }); + + mock_services + .hashing_service_mock + .expect_hash_password() + .returning(move |_| Ok(password.clone())); + + mock_contexts + .user_context_mock + .expect_create() + .with(predicate::eq(user.clone())) + .returning(move |_| Err(DbErr::RecordNotInserted)); //todo!("Needs to be a SqlError with UniqueConstraintViolation with 'username' in message) + + let contexts = disguise_context_mocks(mock_contexts); + let services = disguise_service_mocks(mock_services); + let user_logic = UserController::new(contexts, services); + + let res = user_logic.create_user(create_user_request).await; + assert_eq!(res.unwrap_err().code(), Code::Internal); //todo!("Needs to be code AlreadyExists when mocked Error is corrected) + } + + #[tokio::test] + async fn create_user_invalid_username_returns_error() { + let mock_contexts = get_mock_contexts(); + let mock_services = get_mock_services(); + + let contexts = disguise_context_mocks(mock_contexts); + let services = disguise_service_mocks(mock_services); + let user_logic = UserController::new(contexts, services); + + let create_user_request = Request::new(CreateUserRequest { + email: "valid@email.com".to_string(), + username: "ØØØØØ".to_string(), + password: "123".to_string(), + }); + + let res = user_logic.create_user(create_user_request).await; + assert_eq!(res.unwrap_err().code(), Code::InvalidArgument); + } + + #[tokio::test] + async fn create_user_valid_request_returns_ok() { + let mut mock_contexts = get_mock_contexts(); + let mut mock_services = get_mock_services(); + + let password = "Password123".to_string(); + + let user = user::Model { + id: Default::default(), + email: "newuser@example.com".to_string(), + username: "newuser".to_string(), + password: password.clone(), + }; + + let create_user_request = Request::new(CreateUserRequest { + email: "newuser@example.com".to_string(), + username: "newuser".to_string(), + password: password.clone(), + }); + + mock_services + .hashing_service_mock + .expect_hash_password() + .returning(move |_| Ok(password.clone())); + + mock_contexts + .user_context_mock + .expect_create() + .with(predicate::eq(user.clone())) + .returning(move |_| Ok(user.clone())); + + let contexts = disguise_context_mocks(mock_contexts); + let services = disguise_service_mocks(mock_services); + let user_logic = UserController::new(contexts, services); + + let create_user_response = user_logic.create_user(create_user_request).await; + assert!(create_user_response.is_ok()); + } + + #[tokio::test] + async fn update_user_returns_ok() { + let mut mock_contexts = get_mock_contexts(); + let mut mock_services = get_mock_services(); + + let old_user = user::Model { + id: 1, + email: "olduser@example.com".to_string(), + username: "old_username".to_string(), + password: "StrongPassword123".to_string(), + }; + + let new_user = user::Model { + id: 1, + email: "newuser@example.com".to_string(), + username: "new_username".to_string(), + password: "g76df2gd7hd837g8hjd8723hd8gd823d82d3".to_string(), + }; + + mock_contexts + .user_context_mock + .expect_get_by_id() + .with(predicate::eq(1)) + .returning(move |_| Ok(Some(old_user.clone()))); + + mock_services + .hashing_service_mock + .expect_hash_password() + .with(predicate::eq("StrongPassword123".to_string())) + .returning(move |_| Ok("g76df2gd7hd837g8hjd8723hd8gd823d82d3".to_string())); + + mock_contexts + .user_context_mock + .expect_update() + .with(predicate::eq(new_user.clone())) + .returning(move |_| Ok(new_user.clone())); + + let contexts = disguise_context_mocks(mock_contexts); + let services = disguise_service_mocks(mock_services); + let user_logic = UserController::new(contexts, services); + + let mut update_user_request = Request::new(UpdateUserRequest { + email: Some("newuser@example.com".to_string()), + username: Some("new_username".to_string()), + password: Some("StrongPassword123".to_string()), + }); + + update_user_request + .metadata_mut() + .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); + + let update_user_response = user_logic.update_user(update_user_request).await; + + assert!(update_user_response.is_ok()) + } + + #[tokio::test] + async fn update_user_non_existant_user_returns_err() { + let mut mock_contexts = get_mock_contexts(); + let mock_services = get_mock_services(); + + mock_contexts + .user_context_mock + .expect_get_by_id() + .with(predicate::eq(1)) + .returning(move |_| Err(DbErr::RecordNotFound("".to_string()))); + + let contexts = disguise_context_mocks(mock_contexts); + let services = disguise_service_mocks(mock_services); + let user_logic = UserController::new(contexts, services); + + let mut update_user_request = Request::new(UpdateUserRequest { + email: Some("new_test@test".to_string()), + username: Some("new_test_user".to_string()), + password: Some("new_test_pass".to_string()), + }); + + update_user_request + .metadata_mut() + .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); + + let res = user_logic.update_user(update_user_request).await; + + assert_eq!(res.unwrap_err().code(), Code::Internal); + } + + #[tokio::test] + async fn get_users_returns_ok() { + let mut mock_contexts = get_mock_contexts(); + let mock_services = get_mock_services(); + + let users = vec![ + user::Model { + id: 1, + email: "".to_string(), + username: "".to_string(), + password: "".to_string(), + }, + user::Model { + id: 2, + email: "".to_string(), + username: "".to_string(), + password: "".to_string(), + }, + ]; + + mock_contexts + .user_context_mock + .expect_get_by_ids() + .returning(move |_| Ok(users.clone())); + + let contexts = disguise_context_mocks(mock_contexts); + let services = disguise_service_mocks(mock_services); + let user_logic = UserController::new(contexts, services); + + let get_users_request = Request::new(GetUsersRequest { ids: vec![1, 2] }); + + let get_users_response = user_logic.get_users(get_users_request).await.unwrap(); + + assert_eq!(get_users_response.get_ref().users.len(), 2); + } + + #[tokio::test] + async fn get_users_returns_empty_array() { + let mut mock_contexts = get_mock_contexts(); + let mock_services = get_mock_services(); + + let users: Vec = vec![]; + + mock_contexts + .user_context_mock + .expect_get_by_ids() + .returning(move |_| Ok(users.clone())); + + let contexts = disguise_context_mocks(mock_contexts); + let services = disguise_service_mocks(mock_services); + let user_logic = UserController::new(contexts, services); + + let get_users_request = Request::new(GetUsersRequest { ids: vec![1, 2] }); + + let get_users_response = user_logic.get_users(get_users_request).await.unwrap(); + + assert_eq!(get_users_response.get_ref().users.len(), 0); + } +} diff --git a/ecdar_api_macros/src/lib.rs b/src/lib.rs similarity index 99% rename from ecdar_api_macros/src/lib.rs rename to src/lib.rs index 222bd8d..076f272 100644 --- a/ecdar_api_macros/src/lib.rs +++ b/src/lib.rs @@ -96,7 +96,7 @@ pub fn endpoints(_attr: TokenStream, item: TokenStream) -> TokenStream { })) } } - .into(); + .into(); // It's cursed, but what could be expected from traversing an AST without some kind of pattern. // A method of getting the reference to the "EcdarApiAuth" implementation diff --git a/src/main.rs b/src/main.rs index a397c1b..69b6721 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,7 +11,6 @@ mod contexts; mod controllers; mod entities; mod services; -mod tests; use crate::contexts::context_collection::ContextCollection; use crate::contexts::context_impls::*; diff --git a/src/services/service_impls/reveaal_service.rs b/src/services/service_impls/reveaal_service.rs index 3b20a82..6c757f5 100644 --- a/src/services/service_impls/reveaal_service.rs +++ b/src/services/service_impls/reveaal_service.rs @@ -59,3 +59,36 @@ impl ReveaalServiceTrait for ReveaalService { .await } } + +#[cfg(test)] +mod tests { + // use crate::api::server::server::QueryResponse; + // use wiremock_grpc::generate; + // use wiremock_grpc::*; + // + // generate!("EcdarBackend", MyMockServer); + + #[ignore] + #[tokio::test] + async fn send_query_test_correct_query_returns_ok() { + //todo!("Somehow QueryResponse does not implement prost::message::Message even though it does. + // supposedly a versioning error between wiremock_grpc, tonic, and prost") + + // let mut server = MyMockServer::start_default().await; + // + // let request1 = server.setup( + // MockBuilder::when() + // .path("EcdarBackend/SendQuery") + // .then() + // .return_status(Code::Ok) + // .return_body(|| QueryResponse { + // query_id: 0, + // info: vec![], + // result: None, + // }), + // ); + + //... + //https://crates.io/crates/wiremock-grpc + } +} diff --git a/src/tests/api/auth.rs b/src/tests/api/auth.rs deleted file mode 100644 index 10e8f10..0000000 --- a/src/tests/api/auth.rs +++ /dev/null @@ -1,141 +0,0 @@ -#[cfg(test)] -mod auth { - use crate::api::auth::{RequestExt, Token, TokenError, TokenType}; - use std::{env, str::FromStr}; - use tonic::{metadata::MetadataValue, Request}; - - #[tokio::test] - async fn request_token_trims_bearer() { - let token = "Bearer 1234567890"; - let mut request = Request::new(()); - request - .metadata_mut() - .insert("authorization", MetadataValue::from_str(token).unwrap()); - - let result = request.token_str().unwrap().unwrap(); - - assert_eq!(result, token.trim_start_matches("Bearer ")); - } - - #[tokio::test] - async fn request_token_no_token_returns_none() { - let request = Request::new(()); - let result = request.token_str().unwrap(); - - assert!(result.is_none()); - } - - #[tokio::test] - async fn token_new_access_returns_token() { - env::set_var("ACCESS_TOKEN_HS512_SECRET", "access_secret"); - - let uid = "1"; - let result = Token::new(TokenType::AccessToken, uid); - - assert!(result.is_ok()); - } - - #[tokio::test] - async fn token_new_refresh_returns_token() { - env::set_var("REFRESH_TOKEN_HS512_SECRET", "refresh_secret"); - - let uid = "1"; - let result = Token::new(TokenType::RefreshToken, uid); - - assert!(result.is_ok()); - } - - #[tokio::test] - async fn validate_token_valid_access_returns_tokendata() { - env::set_var("ACCESS_TOKEN_HS512_SECRET", "access_secret"); - - let token = Token::new(TokenType::AccessToken, "1").unwrap(); - let result = token.validate(); - - assert!(result.is_ok()); - } - - #[tokio::test] - async fn validate_token_valid_refresh_returns_tokendata() { - env::set_var("REFRESH_TOKEN_HS512_SECRET", "refresh_secret"); - - let token = Token::new(TokenType::RefreshToken, "1").unwrap(); - let result = token.validate(); - - assert!(result.is_ok()); - } - - #[tokio::test] - async fn validate_token_invalid_returns_err() { - env::set_var("ACCESS_TOKEN_HS512_SECRET", "access_secret"); - env::set_var("REFRESH_TOKEN_HS512_SECRET", "refresh_secret"); - - let result_access = Token::from_str(TokenType::AccessToken, "invalid_token").validate(); - let result_refresh = Token::from_str(TokenType::RefreshToken, "invalid_token").validate(); - - assert_eq!(result_access.unwrap_err(), TokenError::InvalidToken); - assert_eq!(result_refresh.unwrap_err(), TokenError::InvalidToken); - } - - #[tokio::test] - async fn token_type_access_returns_access() { - env::set_var("ACCESS_TOKEN_HS512_SECRET", "access_secret"); - - let token = Token::new(TokenType::AccessToken, "1").unwrap(); - let result = token.token_type(); - - assert_eq!(result, TokenType::AccessToken); - } - - #[tokio::test] - async fn token_type_refresh_returns_refresh() { - env::set_var("REFRESH_TOKEN_HS512_SECRET", "refresh_secret"); - - let token = Token::new(TokenType::RefreshToken, "1").unwrap(); - let result = token.token_type(); - - assert_eq!(result, TokenType::RefreshToken); - } - - #[tokio::test] - async fn token_to_string_returns_string() { - env::set_var("ACCESS_TOKEN_HS512_SECRET", "access_secret"); - - let token = Token::new(TokenType::AccessToken, "1").unwrap(); - let result = token.to_string(); - - assert_eq!(result, token.as_str()); - } - - #[tokio::test] - async fn token_as_str_returns_string() { - env::set_var("ACCESS_TOKEN_HS512_SECRET", "access_secret"); - - let token = Token::new(TokenType::AccessToken, "1").unwrap(); - let result = token.as_str(); - - assert_eq!(result, token.to_string()); - } - - #[tokio::test] - async fn token_from_str_returns_token() { - env::set_var("ACCESS_TOKEN_HS512_SECRET", "access_secret"); - - let token = Token::new(TokenType::AccessToken, "1").unwrap(); - let token_from_str = Token::from_str(TokenType::AccessToken, token.as_str()); - - let result = token_from_str.validate(); - - assert!(result.is_ok()); - } - - #[tokio::test] - async fn token_from_str_invalid_returns_err() { - env::set_var("ACCESS_TOKEN_HS512_SECRET", "access_secret"); - - let token = Token::from_str(TokenType::AccessToken, "invalid_token"); - let result = token.validate(); - - assert!(result.is_err()); - } -} diff --git a/src/tests/api/mod.rs b/src/tests/api/mod.rs deleted file mode 100644 index 8b13789..0000000 --- a/src/tests/api/mod.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/tests/contexts/access_context.rs b/src/tests/contexts/access_context.rs deleted file mode 100644 index 41207c0..0000000 --- a/src/tests/contexts/access_context.rs +++ /dev/null @@ -1,393 +0,0 @@ -use crate::api::server::protobuf::AccessInfo; -use crate::contexts::context_traits::{AccessContextTrait, EntityContextTrait}; -use crate::tests::contexts::helpers::{ - create_accesses, create_projects, create_users, get_reset_database_context, -}; -use crate::{ - contexts::context_impls::AccessContext, - entities::{access, project, user}, - to_active_models, -}; -use sea_orm::{entity::prelude::*, IntoActiveModel}; - -async fn seed_db() -> (AccessContext, access::Model, user::Model, project::Model) { - let db_context = get_reset_database_context().await; - - let access_context = AccessContext::new(db_context); - - let user = create_users(1)[0].clone(); - let project = create_projects(1, user.id)[0].clone(); - let access = create_accesses(1, user.id, project.id)[0].clone(); - - user::Entity::insert(user.clone().into_active_model()) - .exec(&access_context.db_context.get_connection()) - .await - .unwrap(); - project::Entity::insert(project.clone().into_active_model()) - .exec(&access_context.db_context.get_connection()) - .await - .unwrap(); - - (access_context, access, user, project) -} - -// Test the functionality of the 'create' function, which creates a access in the contexts -#[tokio::test] -async fn create_test() { - let (access_context, access, _, _) = seed_db().await; - - let created_access = access_context.create(access.clone()).await.unwrap(); - - let fetched_access = access::Entity::find_by_id(created_access.id) - .one(&access_context.db_context.get_connection()) - .await - .unwrap() - .unwrap(); - - // Assert if the fetched access is the same as the created access - assert_eq!(access, created_access); - assert_eq!(fetched_access, created_access); -} - -#[tokio::test] -async fn create_check_unique_pair_project_id_user_id_test() { - let (access_context, access, _, _) = seed_db().await; - - let _created_access_1 = access_context.create(access.clone()).await.unwrap(); - let _created_access_2 = access_context.create(access.clone()).await; - - assert!(matches!( - _created_access_2.unwrap_err().sql_err(), - Some(SqlErr::UniqueConstraintViolation(_)) - )); -} - -#[tokio::test] -async fn create_invalid_role_test() { - let (access_context, mut access, _, _) = seed_db().await; - - access.role = "abc".into(); - - let created_access = access_context.create(access.clone()).await; - - assert!(matches!( - created_access.unwrap_err().sql_err(), - Some(SqlErr::ForeignKeyConstraintViolation(_)) - )); -} - -#[tokio::test] -async fn create_auto_increment_test() { - let (access_context, _, user, project_1) = seed_db().await; - - let mut project_2 = create_projects(1, user.id)[0].clone(); - project_2.id = project_1.id + 1; - project_2.name = "project_2".to_string(); - - project::Entity::insert(project_2.into_active_model()) - .exec(&access_context.db_context.get_connection()) - .await - .unwrap(); - - let access_1 = access::Model { - id: 0, - role: "Editor".to_string(), - project_id: 1, - user_id: user.id, - }; - - let access_2 = access::Model { - id: 0, - role: "Editor".to_string(), - project_id: 2, - user_id: user.id, - }; - - let created_access1 = access_context.create(access_1.clone()).await.unwrap(); - let created_access2 = access_context.create(access_2.clone()).await.unwrap(); - - let fetched_access1 = access::Entity::find_by_id(created_access1.id) - .one(&access_context.db_context.get_connection()) - .await - .unwrap() - .unwrap(); - - let fetched_access2 = access::Entity::find_by_id(created_access2.id) - .one(&access_context.db_context.get_connection()) - .await - .unwrap() - .unwrap(); - - assert_ne!(fetched_access1.id, fetched_access2.id); - assert_ne!(created_access1.id, created_access2.id); - assert_eq!(created_access1.id, fetched_access1.id); - assert_eq!(created_access2.id, fetched_access2.id); -} - -#[tokio::test] -async fn get_by_id_test() { - let (access_context, access, _, _) = seed_db().await; - - access::Entity::insert(access.clone().into_active_model()) - .exec(&access_context.db_context.get_connection()) - .await - .unwrap(); - - // Fetches the access created using the 'get_by_id' function - let fetched_access = access_context.get_by_id(access.id).await.unwrap().unwrap(); - - // Assert if the fetched access is the same as the created access - assert_eq!(access, fetched_access); -} - -#[tokio::test] -async fn get_by_non_existing_id_test() { - let (access_context, _, _, _) = seed_db().await; - - let fetched_access = access_context.get_by_id(1).await.unwrap(); - - assert!(fetched_access.is_none()); -} - -#[tokio::test] -async fn get_all_test() { - let (access_context, _, user, project) = seed_db().await; - - // Creates a model of the access which will be created - let new_accesses = create_accesses(1, user.id, project.id); - - // Creates the access in the contexts using the 'create' function - access::Entity::insert_many(to_active_models!(new_accesses.clone())) - .exec(&access_context.db_context.get_connection()) - .await - .unwrap(); - - assert_eq!(access_context.get_all().await.unwrap().len(), 1); - - let mut sorted: Vec = new_accesses.clone(); - sorted.sort_by_key(|k| k.id); - - for (i, access) in sorted.into_iter().enumerate() { - assert_eq!(access, new_accesses[i]); - } -} - -#[tokio::test] -async fn get_all_empty_test() { - let (access_context, _, _, _) = seed_db().await; - - let result = access_context.get_all().await.unwrap(); - let empty_accesses: Vec = vec![]; - - assert_eq!(empty_accesses, result); -} - -#[tokio::test] -async fn update_test() { - let (access_context, access, _, _) = seed_db().await; - - access::Entity::insert(access.clone().into_active_model()) - .exec(&access_context.db_context.get_connection()) - .await - .unwrap(); - - let new_access = access::Model { ..access }; - - let updated_access = access_context.update(new_access.clone()).await.unwrap(); - - let fetched_access = access::Entity::find_by_id(updated_access.id) - .one(&access_context.db_context.get_connection()) - .await - .unwrap() - .unwrap(); - - assert_eq!(new_access, updated_access); - assert_eq!(updated_access, fetched_access); -} - -#[tokio::test] -async fn update_modifies_role_test() { - let (access_context, access, _, _) = seed_db().await; - - let access = access::Model { - role: "Editor".into(), - ..access - }; - - access::Entity::insert(access.clone().into_active_model()) - .exec(&access_context.db_context.get_connection()) - .await - .unwrap(); - - let new_access = access::Model { - role: "Commenter".into(), - ..access - }; - - let updated_access = access_context.update(new_access.clone()).await.unwrap(); - - assert_ne!(access, updated_access); - assert_ne!(access, new_access); -} - -#[tokio::test] -async fn update_does_not_modify_id_test() { - let (access_context, access, _, _) = seed_db().await; - access::Entity::insert(access.clone().into_active_model()) - .exec(&access_context.db_context.get_connection()) - .await - .unwrap(); - - let updated_access = access::Model { - id: &access.id + 1, - ..access.clone() - }; - let res = access_context.update(updated_access.clone()).await; - - assert!(matches!(res.unwrap_err(), DbErr::RecordNotUpdated)); -} - -#[tokio::test] -async fn update_does_not_modify_project_id_test() { - let (access_context, access, _, _) = seed_db().await; - - access::Entity::insert(access.clone().into_active_model()) - .exec(&access_context.db_context.get_connection()) - .await - .unwrap(); - - let updated_access = access::Model { - project_id: &access.project_id + 1, - ..access.clone() - }; - let res = access_context.update(updated_access.clone()).await.unwrap(); - - assert_eq!(access, res); -} - -#[tokio::test] -async fn update_does_not_modify_user_id_test() { - let (access_context, access, _, _) = seed_db().await; - - access::Entity::insert(access.clone().into_active_model()) - .exec(&access_context.db_context.get_connection()) - .await - .unwrap(); - - let updated_access = access::Model { - user_id: &access.user_id + 1, - ..access.clone() - }; - let res = access_context.update(updated_access.clone()).await.unwrap(); - - assert_eq!(access, res); -} - -#[tokio::test] -async fn update_invalid_role_test() { - let (access_context, mut access, _, _) = seed_db().await; - - access::Entity::insert(access.clone().into_active_model()) - .exec(&access_context.db_context.get_connection()) - .await - .unwrap(); - - access.role = "abc".into(); - - let updated_access = access_context.update(access.clone()).await; - - assert!(matches!( - updated_access.unwrap_err().sql_err(), - Some(SqlErr::ForeignKeyConstraintViolation(_)) - )); -} - -#[tokio::test] -async fn update_non_existing_id_test() { - let (access_context, access, _, _) = seed_db().await; - - let updated_access = access_context.update(access.clone()).await; - - assert!(matches!( - updated_access.unwrap_err(), - DbErr::RecordNotUpdated - )); -} - -#[tokio::test] -async fn delete_test() { - let (access_context, access, _, _) = seed_db().await; - - access::Entity::insert(access.clone().into_active_model()) - .exec(&access_context.db_context.get_connection()) - .await - .unwrap(); - - let deleted_access = access_context.delete(access.id).await.unwrap(); - - let all_accesses = access::Entity::find() - .all(&access_context.db_context.get_connection()) - .await - .unwrap(); - - assert_eq!(access, deleted_access); - assert!(all_accesses.is_empty()); -} - -#[tokio::test] -async fn delete_non_existing_id_test() { - let (access_context, _, _, _) = seed_db().await; - - let deleted_access = access_context.delete(1).await; - - assert!(matches!( - deleted_access.unwrap_err(), - DbErr::RecordNotFound(_) - )); -} - -#[tokio::test] -async fn get_by_uid_and_project_id_test() { - let (access_context, expected_access, user, project) = seed_db().await; - - access::Entity::insert(expected_access.clone().into_active_model()) - .exec(&access_context.db_context.get_connection()) - .await - .unwrap(); - - let access = access_context - .get_access_by_uid_and_project_id(user.id, project.id) - .await; - - assert_eq!(access.unwrap().unwrap(), expected_access); -} - -#[tokio::test] -async fn get_access_by_project_id_test_returns_ok() { - let (access_context, expected_access, _, model) = seed_db().await; - - let expected_access_access_info_vector = vec![AccessInfo { - id: expected_access.id, - project_id: expected_access.project_id, - user_id: expected_access.user_id, - role: expected_access.role.clone(), - }]; - - access::Entity::insert(expected_access.clone().into_active_model()) - .exec(&access_context.db_context.get_connection()) - .await - .unwrap(); - - let access = access_context.get_access_by_project_id(model.id).await; - - assert!(access.unwrap() == expected_access_access_info_vector); -} - -#[tokio::test] -async fn get_access_by_project_id_test_returns_empty() { - let (access_context, _, _, model) = seed_db().await; - - let access = access_context.get_access_by_project_id(model.id).await; - - assert!(access.unwrap().is_empty()); -} diff --git a/src/tests/contexts/helpers.rs b/src/tests/contexts/helpers.rs deleted file mode 100644 index 2286f5b..0000000 --- a/src/tests/contexts/helpers.rs +++ /dev/null @@ -1,120 +0,0 @@ -#![cfg(test)] - -use crate::contexts::context_impls::{PostgresDatabaseContext, SQLiteDatabaseContext}; -use crate::contexts::context_traits::DatabaseContextTrait; -use crate::entities::{access, in_use, project, query, session, user}; -use dotenv::dotenv; -use sea_orm::{ConnectionTrait, Database, DbBackend}; -use std::env; -use std::sync::Arc; - -pub async fn get_reset_database_context() -> Arc { - dotenv().ok(); - - let url = env::var("TEST_DATABASE_URL").expect("TEST_DATABASE_URL must be set to run tests."); - let db = Database::connect(&url).await.unwrap(); - let db_context: Arc = match db.get_database_backend() { - DbBackend::Sqlite => Arc::new(SQLiteDatabaseContext::new(&url).await.unwrap()), - DbBackend::Postgres => Arc::new(PostgresDatabaseContext::new(&url).await.unwrap()), - _ => panic!("Database protocol not supported"), - }; - - db_context.reset().await.unwrap() -} - -/// -/// -/// # Arguments -/// -/// * `amount`: -/// * `model`: -/// -/// returns: Vec -/// -/// # Examples -/// -/// ``` -/// let vector: Vec = create_entities(3,|x| UserModel { -/// id: &x+i, -/// email: format!("mail{}@mail.dk",&x), -/// username: format!("username{}", &x), -/// password: format!("qwerty{}", &x), -/// ); -/// ``` - -pub fn create_entities(amount: i32, project_creator: F) -> Vec -where - F: Fn(i32) -> M, -{ - let mut vector: Vec = vec![]; - for i in 0..amount { - vector.push(project_creator(i)); - } - vector -} - -pub fn create_users(amount: i32) -> Vec { - create_entities(amount, |i| user::Model { - id: i + 1, - email: format!("mail{}@mail.dk", &i), - username: format!("username{}", &i), - password: format!("qwerty{}", &i), - }) -} - -pub fn create_projects(amount: i32, user_id: i32) -> Vec { - create_entities(amount, |i| project::Model { - id: i + 1, - name: format!("name {}", i), - components_info: "{}".to_owned().parse().unwrap(), - owner_id: user_id, - }) -} - -pub fn create_accesses(amount: i32, user_id: i32, project_id: i32) -> Vec { - create_entities(amount, |i| access::Model { - id: i + 1, - role: "Reader".into(), - project_id: project_id + i, - user_id: user_id + i, - }) -} - -pub fn create_sessions(amount: i32, user_id: i32) -> Vec { - create_entities(amount, |i| session::Model { - id: i + 1, - refresh_token: "test_refresh_token".to_string() + format!("{}", i).as_str(), - access_token: "test_access_token".to_string() + format!("{}", i).as_str(), - user_id, - updated_at: Default::default(), - }) -} - -pub fn create_in_uses(amount: i32, project_id: i32, session_id: i32) -> Vec { - create_entities(amount, |i| in_use::Model { - project_id: project_id + i, - session_id, - latest_activity: Default::default(), - }) -} - -pub fn create_queries(amount: i32, project_id: i32) -> Vec { - create_entities(amount, |i| query::Model { - id: i + 1, - string: "".to_string(), - result: None, - outdated: true, - project_id, - }) -} - -#[macro_export] -macro_rules! to_active_models { - ($vec:expr) => {{ - let mut models = Vec::new(); - for model in $vec { - models.push(model.into_active_model()); - } - models - }}; -} diff --git a/src/tests/contexts/in_use_context.rs b/src/tests/contexts/in_use_context.rs deleted file mode 100644 index fd21241..0000000 --- a/src/tests/contexts/in_use_context.rs +++ /dev/null @@ -1,271 +0,0 @@ -use crate::tests::contexts::helpers::*; -use crate::{ - contexts::context_impls::InUseContext, - contexts::context_traits::EntityContextTrait, - entities::{in_use, project, session, user}, - to_active_models, -}; -use chrono::{Duration, Utc}; -use sea_orm::{entity::prelude::*, IntoActiveModel}; -use std::matches; -use std::ops::Add; - -async fn seed_db() -> ( - InUseContext, - in_use::Model, - session::Model, - project::Model, - user::Model, -) { - let db_context = get_reset_database_context().await; - - let in_use_context = InUseContext::new(db_context); - - let user = create_users(1)[0].clone(); - let project = create_projects(1, user.id)[0].clone(); - let session = create_sessions(1, user.id)[0].clone(); - let in_use = create_in_uses(1, project.id, session.id)[0].clone(); - - user::Entity::insert(user.clone().into_active_model()) - .exec(&in_use_context.db_context.get_connection()) - .await - .unwrap(); - project::Entity::insert(project.clone().into_active_model()) - .exec(&in_use_context.db_context.get_connection()) - .await - .unwrap(); - session::Entity::insert(session.clone().into_active_model()) - .exec(&in_use_context.db_context.get_connection()) - .await - .unwrap(); - - (in_use_context, in_use, session, project, user) -} - -#[tokio::test] -async fn create_test() { - let (in_use_context, mut in_use, _, _, _) = seed_db().await; - - let inserted_in_use = in_use_context.create(in_use.clone()).await.unwrap(); - - in_use.latest_activity = inserted_in_use.latest_activity; - - let fetched_in_use = in_use::Entity::find_by_id(inserted_in_use.clone().project_id) - .one(&in_use_context.db_context.get_connection()) - .await - .unwrap() - .unwrap(); - - assert_eq!(in_use, inserted_in_use); - assert_eq!(in_use, fetched_in_use); -} - -#[tokio::test] -async fn create_default_latest_activity_test() { - let t_min = Utc::now().timestamp(); - - let (in_use_context, in_use, _, _, _) = seed_db().await; - - let inserted_in_use = in_use_context.create(in_use.clone()).await.unwrap(); - - let fetched_in_use = in_use::Entity::find_by_id(inserted_in_use.project_id) - .one(&in_use_context.db_context.get_connection()) - .await - .unwrap() - .unwrap(); - - let t_max = Utc::now().timestamp(); - - let t_actual = fetched_in_use.clone().latest_activity.timestamp(); - - assert!(t_min <= t_actual && t_actual <= t_max) -} - -#[tokio::test] -async fn get_by_id_test() { - let (in_use_context, in_use, _, _, _) = seed_db().await; - - in_use::Entity::insert(in_use.clone().into_active_model()) - .exec(&in_use_context.db_context.get_connection()) - .await - .unwrap(); - - let fetched_in_use = in_use_context - .get_by_id(in_use.project_id) - .await - .unwrap() - .unwrap(); - - assert_eq!(fetched_in_use, in_use) -} - -#[tokio::test] -async fn get_by_non_existing_id_test() { - let (in_use_context, _in_use, _, _, _) = seed_db().await; - - let in_use = in_use_context.get_by_id(1).await; - - assert!(in_use.unwrap().is_none()) -} - -#[tokio::test] -async fn get_all_test() { - let (in_use_context, _in_use, session, project, _user) = seed_db().await; - - let in_uses = create_in_uses(1, project.id, session.id); - - in_use::Entity::insert_many(to_active_models!(in_uses.clone())) - .exec(&in_use_context.db_context.get_connection()) - .await - .unwrap(); - - assert_eq!(in_use_context.get_all().await.unwrap().len(), 1); -} - -#[tokio::test] -async fn get_all_empty_test() { - let (in_use_context, _, _, _, _) = seed_db().await; - - let in_uses = in_use_context.get_all().await.unwrap(); - - assert_eq!(0, in_uses.len()) -} - -#[tokio::test] -async fn update_test() { - let (in_use_context, in_use, _, _, _) = seed_db().await; - - in_use::Entity::insert(in_use.clone().into_active_model()) - .exec(&in_use_context.db_context.get_connection()) - .await - .unwrap(); - - let new_in_use = in_use::Model { ..in_use }; - - let updated_in_use = in_use_context.update(new_in_use.clone()).await.unwrap(); - - let fetched_in_use = in_use::Entity::find_by_id(updated_in_use.project_id) - .one(&in_use_context.db_context.get_connection()) - .await - .unwrap() - .unwrap(); - - assert_eq!(new_in_use, updated_in_use); - assert_eq!(updated_in_use, fetched_in_use); -} - -#[tokio::test] -async fn update_modifies_latest_activity_test() { - let (in_use_context, in_use, _, _, _) = seed_db().await; - - in_use::Entity::insert(in_use.clone().into_active_model()) - .exec(&in_use_context.db_context.get_connection()) - .await - .unwrap(); - - let new_in_use = in_use::Model { - latest_activity: in_use.clone().latest_activity.add(Duration::seconds(1)), - ..in_use - }; - - let updated_in_use = in_use_context.update(new_in_use.clone()).await.unwrap(); - - assert_ne!(in_use, updated_in_use); - assert_ne!(in_use, new_in_use); -} - -#[tokio::test] -async fn update_modifies_session_id_test() { - let (in_use_context, in_use, _, _, _) = seed_db().await; - - in_use::Entity::insert(in_use.clone().into_active_model()) - .exec(&in_use_context.db_context.get_connection()) - .await - .unwrap(); - - let mut session2 = create_sessions(1, in_use.session_id)[0].clone(); - session2.id = in_use.session_id + 1; - session2.refresh_token = "new_refresh_token".to_string(); - session2.access_token = "new_access_token".to_string(); - - session::Entity::insert(session2.clone().into_active_model()) - .exec(&in_use_context.db_context.get_connection()) - .await - .unwrap(); - - let new_in_use = in_use::Model { - session_id: in_use.session_id + 1, - ..in_use - }; - - let updated_in_use = in_use_context.update(new_in_use.clone()).await.unwrap(); - - assert_ne!(in_use, updated_in_use); - assert_ne!(in_use, new_in_use); -} - -#[tokio::test] -async fn update_does_not_modify_project_id_test() { - let (in_use_context, in_use, _, _, _) = seed_db().await; - - in_use::Entity::insert(in_use.clone().into_active_model()) - .exec(&in_use_context.db_context.get_connection()) - .await - .unwrap(); - - let updated_in_use = in_use::Model { - project_id: in_use.project_id + 1, - ..in_use.clone() - }; - - let updated_in_use = in_use_context.update(updated_in_use.clone()).await; - - assert!(matches!( - updated_in_use.unwrap_err(), - DbErr::RecordNotUpdated - )); -} - -#[tokio::test] -async fn update_non_existing_id_test() { - let (in_use_context, in_use, _, _, _) = seed_db().await; - - let updated_in_use = in_use_context.update(in_use.clone()).await; - - assert!(matches!( - updated_in_use.unwrap_err(), - DbErr::RecordNotUpdated - )); -} - -#[tokio::test] -async fn delete_test() { - let (in_use_context, in_use, _, _, _) = seed_db().await; - - in_use::Entity::insert(in_use.clone().into_active_model()) - .exec(&in_use_context.db_context.get_connection()) - .await - .unwrap(); - - let deleted_in_use = in_use_context.delete(in_use.project_id).await.unwrap(); - - let all_in_uses = in_use::Entity::find() - .all(&in_use_context.db_context.get_connection()) - .await - .unwrap(); - - assert_eq!(in_use, deleted_in_use); - assert!(all_in_uses.is_empty()); -} - -#[tokio::test] -async fn delete_non_existing_id_test() { - let (in_use_context, _, _, _, _) = seed_db().await; - - let deleted_in_use = in_use_context.delete(1).await; - - assert!(matches!( - deleted_in_use.unwrap_err(), - DbErr::RecordNotFound(_) - )) -} diff --git a/src/tests/contexts/mod.rs b/src/tests/contexts/mod.rs deleted file mode 100644 index 1630fab..0000000 --- a/src/tests/contexts/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod helpers; diff --git a/src/tests/contexts/project_context.rs b/src/tests/contexts/project_context.rs deleted file mode 100644 index 3d79071..0000000 --- a/src/tests/contexts/project_context.rs +++ /dev/null @@ -1,409 +0,0 @@ -use crate::tests::contexts::helpers::*; -use crate::{ - contexts::context_impls::ProjectContext, - contexts::context_traits::EntityContextTrait, - entities::{access, in_use, project, query, session, user}, - to_active_models, -}; -use sea_orm::error::DbErr; -use sea_orm::{entity::prelude::*, IntoActiveModel}; -use std::matches; - -async fn seed_db() -> (ProjectContext, project::Model, user::Model) { - let db_context = get_reset_database_context().await; - - let project_context = ProjectContext::new(db_context); - - let user = create_users(1)[0].clone(); - let project = create_projects(1, user.id)[0].clone(); - - user::Entity::insert(user.clone().into_active_model()) - .exec(&project_context.db_context.get_connection()) - .await - .unwrap(); - - (project_context, project, user) -} - -#[tokio::test] -async fn create_test() { - let (project_context, project, _) = seed_db().await; - - let created_project = project_context.create(project.clone()).await.unwrap(); - - let fetched_project = project::Entity::find_by_id(created_project.id) - .one(&project_context.db_context.get_connection()) - .await - .unwrap() - .unwrap(); - - assert_eq!(project, created_project); - assert_eq!(fetched_project, created_project); -} - -#[tokio::test] -async fn create_auto_increment_test() { - let (project_context, project, _) = seed_db().await; - - let projects = create_projects(2, project.owner_id); - - let created_project1 = project_context.create(projects[0].clone()).await.unwrap(); - let created_project2 = project_context.create(projects[1].clone()).await.unwrap(); - - let fetched_project1 = project::Entity::find_by_id(created_project1.id) - .one(&project_context.db_context.get_connection()) - .await - .unwrap() - .unwrap(); - - let fetched_project2 = project::Entity::find_by_id(created_project2.id) - .one(&project_context.db_context.get_connection()) - .await - .unwrap() - .unwrap(); - - assert_ne!(fetched_project1.id, fetched_project2.id); - assert_ne!(created_project1.id, created_project2.id); - assert_eq!(created_project1.id, fetched_project1.id); - assert_eq!(created_project2.id, fetched_project2.id); -} - -#[tokio::test] -async fn get_by_id_test() { - let (project_context, project, _) = seed_db().await; - - project::Entity::insert(project.clone().into_active_model()) - .exec(&project_context.db_context.get_connection()) - .await - .unwrap(); - - let fetched_project = project_context - .get_by_id(project.id) - .await - .unwrap() - .unwrap(); - - assert_eq!(project, fetched_project); -} - -#[tokio::test] -async fn get_by_non_existing_id_test() { - let (project_context, _, _) = seed_db().await; - - let fetched_project = project_context.get_by_id(1).await.unwrap(); - - assert!(fetched_project.is_none()); -} - -#[tokio::test] -async fn get_all_test() { - let (project_context, _, user) = seed_db().await; - - let new_projects = create_projects(3, user.id); - - project::Entity::insert_many(to_active_models!(new_projects.clone())) - .exec(&project_context.db_context.get_connection()) - .await - .unwrap(); - - assert_eq!(project_context.get_all().await.unwrap().len(), 3); - - let mut sorted = new_projects.clone(); - sorted.sort_by_key(|k| k.id); - - for (i, project) in sorted.into_iter().enumerate() { - assert_eq!(project, new_projects[i]); - } -} - -#[tokio::test] -async fn get_all_empty_test() { - let (project_context, _, _) = seed_db().await; - - let result = project_context.get_all().await.unwrap(); - let empty_projects: Vec = vec![]; - - assert_eq!(empty_projects, result); -} - -#[tokio::test] -async fn update_test() { - let (project_context, project, _) = seed_db().await; - - project::Entity::insert(project.clone().into_active_model()) - .exec(&project_context.db_context.get_connection()) - .await - .unwrap(); - - let new_project = project::Model { ..project }; - - let updated_project = project_context.update(new_project.clone()).await.unwrap(); - - let fetched_project = project::Entity::find_by_id(updated_project.id) - .one(&project_context.db_context.get_connection()) - .await - .unwrap() - .unwrap(); - - assert_eq!(new_project, updated_project); - assert_eq!(updated_project, fetched_project); -} - -#[tokio::test] -async fn update_modifies_name_test() { - let (project_context, project, _) = seed_db().await; - - let project = project::Model { - name: "project1".into(), - ..project.clone() - }; - - project::Entity::insert(project.clone().into_active_model()) - .exec(&project_context.db_context.get_connection()) - .await - .unwrap(); - - let new_project = project::Model { - name: "project2".into(), - ..project.clone() - }; - - let updated_project = project_context.update(new_project.clone()).await.unwrap(); - - assert_ne!(project, updated_project); - assert_ne!(project, new_project); -} - -#[tokio::test] -async fn update_modifies_components_info_test() { - let (project_context, project, _) = seed_db().await; - - let project = project::Model { - components_info: "{\"a\":1}".to_owned().parse().unwrap(), - ..project.clone() - }; - - project::Entity::insert(project.clone().into_active_model()) - .exec(&project_context.db_context.get_connection()) - .await - .unwrap(); - - let new_project = project::Model { - components_info: "{\"a\":2}".to_owned().parse().unwrap(), - ..project.clone() - }; - - let updated_project = project_context.update(new_project.clone()).await.unwrap(); - - assert_ne!(project, updated_project); - assert_ne!(project, new_project); -} - -#[tokio::test] -async fn update_does_not_modify_id_test() { - let (project_context, project, _) = seed_db().await; - - project::Entity::insert(project.clone().into_active_model()) - .exec(&project_context.db_context.get_connection()) - .await - .unwrap(); - - let new_project = project::Model { - id: &project.id + 1, - ..project.clone() - }; - - let res = project_context.update(new_project.clone()).await; - - assert!(matches!(res.unwrap_err(), DbErr::RecordNotUpdated)); -} - -#[tokio::test] -async fn update_does_not_modify_owner_id_test() { - let (project_context, project, _) = seed_db().await; - - project::Entity::insert(project.clone().into_active_model()) - .exec(&project_context.db_context.get_connection()) - .await - .unwrap(); - - let new_project = project::Model { - owner_id: &project.owner_id + 1, - ..project.clone() - }; - - let res = project_context.update(new_project.clone()).await.unwrap(); - - assert_eq!(project, res); -} - -#[tokio::test] -async fn update_check_query_outdated_test() { - let (project_context, project, _) = seed_db().await; - - let mut query = create_queries(1, project.id)[0].clone(); - - query.outdated = false; - - project::Entity::insert(project.clone().into_active_model()) - .exec(&project_context.db_context.get_connection()) - .await - .unwrap(); - - query::Entity::insert(query.clone().into_active_model()) - .exec(&project_context.db_context.get_connection()) - .await - .unwrap(); - - let new_project = project::Model { ..project }; - - let updated_project = project_context.update(new_project.clone()).await.unwrap(); - - let fetched_query = query::Entity::find_by_id(updated_project.id) - .one(&project_context.db_context.get_connection()) - .await - .unwrap() - .unwrap(); - - assert!(fetched_query.outdated); -} - -#[tokio::test] -async fn update_non_existing_id_test() { - let (project_context, project, _) = seed_db().await; - - let updated_project = project_context.update(project.clone()).await; - - assert!(matches!( - updated_project.unwrap_err(), - DbErr::RecordNotUpdated - )); -} - -#[tokio::test] -async fn delete_test() { - // Setting up contexts and user context - let (project_context, project, _) = seed_db().await; - - project::Entity::insert(project.clone().into_active_model()) - .exec(&project_context.db_context.get_connection()) - .await - .unwrap(); - - let deleted_project = project_context.delete(project.id).await.unwrap(); - - let all_projects = project::Entity::find() - .all(&project_context.db_context.get_connection()) - .await - .unwrap(); - - assert_eq!(project, deleted_project); - assert_eq!(all_projects.len(), 0); -} - -#[tokio::test] -async fn delete_cascade_query_test() { - let (project_context, project, _) = seed_db().await; - - let query = create_queries(1, project.clone().id)[0].clone(); - - project::Entity::insert(project.clone().into_active_model()) - .exec(&project_context.db_context.get_connection()) - .await - .unwrap(); - query::Entity::insert(query.clone().into_active_model()) - .exec(&project_context.db_context.get_connection()) - .await - .unwrap(); - - project_context.delete(project.id).await.unwrap(); - - let all_queries = query::Entity::find() - .all(&project_context.db_context.get_connection()) - .await - .unwrap(); - let all_projects = project::Entity::find() - .all(&project_context.db_context.get_connection()) - .await - .unwrap(); - - assert_eq!(all_queries.len(), 0); - assert_eq!(all_projects.len(), 0); -} - -#[tokio::test] -async fn delete_cascade_access_test() { - let (project_context, project, _) = seed_db().await; - - let access = create_accesses(1, 1, project.clone().id)[0].clone(); - - project::Entity::insert(project.clone().into_active_model()) - .exec(&project_context.db_context.get_connection()) - .await - .unwrap(); - access::Entity::insert(access.clone().into_active_model()) - .exec(&project_context.db_context.get_connection()) - .await - .unwrap(); - - project_context.delete(project.id).await.unwrap(); - - let all_projects = project::Entity::find() - .all(&project_context.db_context.get_connection()) - .await - .unwrap(); - let all_accesses = access::Entity::find() - .all(&project_context.db_context.get_connection()) - .await - .unwrap(); - - assert_eq!(all_projects.len(), 0); - assert_eq!(all_accesses.len(), 0); -} - -#[tokio::test] -async fn delete_cascade_in_use_test() { - let (project_context, project, user) = seed_db().await; - - let session = create_sessions(1, user.clone().id)[0].clone(); - let in_use = create_in_uses(1, project.clone().id, 1)[0].clone(); - - session::Entity::insert(session.clone().into_active_model()) - .exec(&project_context.db_context.get_connection()) - .await - .unwrap(); - project::Entity::insert(project.clone().into_active_model()) - .exec(&project_context.db_context.get_connection()) - .await - .unwrap(); - in_use::Entity::insert(in_use.clone().into_active_model()) - .exec(&project_context.db_context.get_connection()) - .await - .unwrap(); - - project_context.delete(project.id).await.unwrap(); - - let all_projects = project::Entity::find() - .all(&project_context.db_context.get_connection()) - .await - .unwrap(); - let all_in_uses = in_use::Entity::find() - .all(&project_context.db_context.get_connection()) - .await - .unwrap(); - - assert_eq!(all_projects.len(), 0); - assert_eq!(all_in_uses.len(), 0); -} - -#[tokio::test] -async fn delete_non_existing_id_test() { - let (project_context, _, _) = seed_db().await; - - let deleted_project = project_context.delete(1).await; - - assert!(matches!( - deleted_project.unwrap_err(), - DbErr::RecordNotFound(_) - )); -} diff --git a/src/tests/contexts/query_context.rs b/src/tests/contexts/query_context.rs deleted file mode 100644 index 65f6734..0000000 --- a/src/tests/contexts/query_context.rs +++ /dev/null @@ -1,315 +0,0 @@ -use crate::tests::contexts::helpers::{ - create_projects, create_queries, create_users, get_reset_database_context, -}; -use crate::{ - contexts::context_impls::QueryContext, - contexts::context_traits::EntityContextTrait, - entities::{project, query, user}, - to_active_models, -}; -use sea_orm::{entity::prelude::*, IntoActiveModel}; - -async fn seed_db() -> (QueryContext, query::Model, project::Model) { - let db_context = get_reset_database_context().await; - - let query_context = QueryContext::new(db_context); - - let user = create_users(1)[0].clone(); - let project = create_projects(1, user.id)[0].clone(); - let query = create_queries(1, project.id)[0].clone(); - - user::Entity::insert(user.clone().into_active_model()) - .exec(&query_context.db_context.get_connection()) - .await - .unwrap(); - project::Entity::insert(project.clone().into_active_model()) - .exec(&query_context.db_context.get_connection()) - .await - .unwrap(); - - (query_context, query, project) -} - -#[tokio::test] -async fn create_test() { - let (query_context, query, _) = seed_db().await; - - let created_query = query_context.create(query.clone()).await.unwrap(); - - let fetched_query = query::Entity::find_by_id(created_query.id) - .one(&query_context.db_context.get_connection()) - .await - .unwrap() - .unwrap(); - - // Assert if the fetched access is the same as the created access - assert_eq!(query, created_query); - assert_eq!(fetched_query, created_query); -} - -#[tokio::test] -async fn create_default_outdated_test() { - let (query_context, query, _) = seed_db().await; - - let _inserted_query = query_context.create(query.clone()).await.unwrap(); - - let fetched_query = query::Entity::find_by_id(query.project_id) - .one(&query_context.db_context.get_connection()) - .await - .unwrap() - .unwrap(); - - assert!(fetched_query.outdated) -} - -#[tokio::test] -async fn create_auto_increment_test() { - let (query_context, query, _) = seed_db().await; - - let created_query1 = query_context.create(query.clone()).await.unwrap(); - let created_query2 = query_context.create(query.clone()).await.unwrap(); - - let fetched_query1 = query::Entity::find_by_id(created_query1.id) - .one(&query_context.db_context.get_connection()) - .await - .unwrap() - .unwrap(); - - let fetched_query2 = query::Entity::find_by_id(created_query2.id) - .one(&query_context.db_context.get_connection()) - .await - .unwrap() - .unwrap(); - - assert_ne!(fetched_query1.id, fetched_query2.id); - assert_ne!(created_query1.id, created_query2.id); - assert_eq!(created_query1.id, fetched_query1.id); - assert_eq!(created_query2.id, fetched_query2.id); -} - -#[tokio::test] -async fn get_by_id_test() { - let (query_context, query, _) = seed_db().await; - - query::Entity::insert(query.clone().into_active_model()) - .exec(&query_context.db_context.get_connection()) - .await - .unwrap(); - - let fetched_in_use = query_context - .get_by_id(query.project_id) - .await - .unwrap() - .unwrap(); - - assert_eq!(fetched_in_use, query) -} - -#[tokio::test] -async fn get_by_non_existing_id_test() { - let (query_context, _, _) = seed_db().await; - - let query = query_context.get_by_id(1).await; - - assert!(query.unwrap().is_none()) -} - -#[tokio::test] -async fn get_all_test() { - let (query_context, _, project) = seed_db().await; - - let queries = create_queries(10, project.id); - - query::Entity::insert_many(to_active_models!(queries.clone())) - .exec(&query_context.db_context.get_connection()) - .await - .unwrap(); - - assert_eq!(query_context.get_all().await.unwrap().len(), 10); - - let mut sorted = queries.clone(); - sorted.sort_by_key(|k| k.project_id); - - for (i, query) in sorted.into_iter().enumerate() { - assert_eq!(query, queries[i]); - } -} - -#[tokio::test] -async fn get_all_empty_test() { - let (query_context, _, _) = seed_db().await; - - let queries = query_context.get_all().await.unwrap(); - - assert_eq!(0, queries.len()) -} - -#[tokio::test] -async fn update_test() { - let (query_context, query, _) = seed_db().await; - - query::Entity::insert(query.clone().into_active_model()) - .exec(&query_context.db_context.get_connection()) - .await - .unwrap(); - - let new_query = query::Model { ..query }; - - let updated_query = query_context.update(new_query.clone()).await.unwrap(); - - let fetched_query = query::Entity::find_by_id(updated_query.project_id) - .one(&query_context.db_context.get_connection()) - .await - .unwrap() - .unwrap(); - - assert_eq!(new_query, updated_query); - assert_eq!(updated_query, fetched_query); -} - -#[tokio::test] -async fn update_modifies_string_test() { - let (query_context, query, _) = seed_db().await; - - query::Entity::insert(query.clone().into_active_model()) - .exec(&query_context.db_context.get_connection()) - .await - .unwrap(); - - let new_query = query::Model { - string: query.clone().string + "123", - ..query.clone() - }; - - let updated_query = query_context.update(new_query.clone()).await.unwrap(); - - assert_ne!(query, updated_query); - assert_ne!(query, new_query); -} - -#[tokio::test] -async fn update_modifies_outdated_test() { - let (query_context, query, _) = seed_db().await; - - query::Entity::insert(query.clone().into_active_model()) - .exec(&query_context.db_context.get_connection()) - .await - .unwrap(); - - let new_query = query::Model { - outdated: !query.clone().outdated, - ..query.clone() - }; - - let updated_query = query_context.update(new_query.clone()).await.unwrap(); - - assert_ne!(query, updated_query); - assert_ne!(query, new_query); -} - -#[tokio::test] -async fn update_modifies_result_test() { - let (query_context, mut query, _) = seed_db().await; - - query.result = Some("{}".to_owned().parse().unwrap()); - - query::Entity::insert(query.clone().into_active_model()) - .exec(&query_context.db_context.get_connection()) - .await - .unwrap(); - - let new_query = query::Model { - result: None, - ..query.clone() - }; - - let updated_query = query_context.update(new_query.clone()).await.unwrap(); - - assert_ne!(query, updated_query); - assert_ne!(query, new_query); -} - -#[tokio::test] -async fn update_does_not_modify_id_test() { - let (query_context, query, _) = seed_db().await; - - query::Entity::insert(query.clone().into_active_model()) - .exec(&query_context.db_context.get_connection()) - .await - .unwrap(); - - let new_query = query::Model { - id: query.id + 1, - ..query.clone() - }; - - let updated_query = query_context.update(new_query.clone()).await; - - assert!(matches!( - updated_query.unwrap_err(), - DbErr::RecordNotUpdated - )); -} - -#[tokio::test] -async fn update_does_not_modify_project_id_test() { - let (query_context, query, _) = seed_db().await; - - query::Entity::insert(query.clone().into_active_model()) - .exec(&query_context.db_context.get_connection()) - .await - .unwrap(); - - let new_query = query::Model { - project_id: query.project_id + 1, - ..query.clone() - }; - - let updated_query = query_context.update(new_query.clone()).await.unwrap(); - - assert_eq!(query, updated_query); -} - -#[tokio::test] -async fn update_non_existing_id_test() { - let (query_context, query, _) = seed_db().await; - - let updated_query = query_context.update(query.clone()).await; - - assert!(matches!( - updated_query.unwrap_err(), - DbErr::RecordNotUpdated - )); -} - -#[tokio::test] -async fn delete_test() { - let (query_context, query, _) = seed_db().await; - - query::Entity::insert(query.clone().into_active_model()) - .exec(&query_context.db_context.get_connection()) - .await - .unwrap(); - - let deleted_query = query_context.delete(query.project_id).await.unwrap(); - - let all_queries = query::Entity::find() - .all(&query_context.db_context.get_connection()) - .await - .unwrap(); - - assert_eq!(query, deleted_query); - assert!(all_queries.is_empty()); -} - -#[tokio::test] -async fn delete_non_existing_id_test() { - let (query_context, _, _) = seed_db().await; - - let deleted_query = query_context.delete(1).await; - - assert!(matches!( - deleted_query.unwrap_err(), - DbErr::RecordNotFound(_) - )) -} diff --git a/src/tests/contexts/session_context.rs b/src/tests/contexts/session_context.rs deleted file mode 100644 index 5fed383..0000000 --- a/src/tests/contexts/session_context.rs +++ /dev/null @@ -1,422 +0,0 @@ -use crate::{api::auth::TokenType, tests::contexts::helpers::*}; -use sea_orm::{entity::prelude::*, IntoActiveModel}; -use std::ops::Add; - -use crate::{ - contexts::context_impls::SessionContext, - contexts::context_traits::{EntityContextTrait, SessionContextTrait}, - entities::{in_use, project, session, user}, - to_active_models, -}; - -use chrono::{Duration, Utc}; - -async fn seed_db() -> (SessionContext, session::Model, user::Model, project::Model) { - let db_context = get_reset_database_context().await; - - let session_context = SessionContext::new(db_context); - - let user = create_users(1)[0].clone(); - let project = create_projects(1, user.id)[0].clone(); - let session = create_sessions(1, user.id)[0].clone(); - - user::Entity::insert(user.clone().into_active_model()) - .exec(&session_context.db_context.get_connection()) - .await - .unwrap(); - project::Entity::insert(project.clone().into_active_model()) - .exec(&session_context.db_context.get_connection()) - .await - .unwrap(); - - (session_context, session, user, project) -} - -#[tokio::test] -async fn create_test() { - // Setting up a sqlite contexts in memory. - let (session_context, mut session, _, _) = seed_db().await; - - let created_session = session_context.create(session.clone()).await.unwrap(); - - session.updated_at = created_session.updated_at; - - let fetched_session = session::Entity::find_by_id(created_session.id) - .one(&session_context.db_context.get_connection()) - .await - .unwrap() - .unwrap(); - - assert_eq!(session, created_session); - assert_eq!(fetched_session, created_session); -} - -#[tokio::test] -async fn create_default_created_at_test() { - let t_min = Utc::now().timestamp(); - - let (session_context, session, _, _) = seed_db().await; - - let _created_session = session_context.create(session.clone()).await.unwrap(); - - let fetched_session = session::Entity::find_by_id(1) - .one(&session_context.db_context.get_connection()) - .await - .unwrap() - .unwrap(); - - let t_max = Utc::now().timestamp(); - let t_actual = fetched_session.clone().updated_at.timestamp(); - - assert!(t_min <= t_actual && t_actual <= t_max) -} - -#[tokio::test] -async fn create_auto_increment_test() { - // Setting up contexts and session context - let (session_context, _, user, _) = seed_db().await; - - let sessions = create_sessions(2, user.id); - - // Creates the sessions in the contexts using the 'create' function - let created_session1 = session_context.create(sessions[0].clone()).await.unwrap(); - let created_session2 = session_context.create(sessions[1].clone()).await.unwrap(); - - let fetched_session1 = session::Entity::find_by_id(created_session1.id) - .one(&session_context.db_context.get_connection()) - .await - .unwrap() - .unwrap(); - - let fetched_session2 = session::Entity::find_by_id(created_session2.id) - .one(&session_context.db_context.get_connection()) - .await - .unwrap() - .unwrap(); - - // Assert if the new_session, created_session, and fetched_session are the same - assert_ne!(fetched_session1.id, fetched_session2.id); - assert_ne!(created_session1.id, created_session2.id); - assert_eq!(created_session1.id, fetched_session1.id); - assert_eq!(created_session2.id, fetched_session2.id); -} - -#[tokio::test] -async fn create_non_unique_refresh_token_test() { - let (session_context, _, _, user) = seed_db().await; - - let mut sessions = create_sessions(2, user.id); - - sessions[1].refresh_token = sessions[0].refresh_token.clone(); - - let _created_session1 = session_context.create(sessions[0].clone()).await.unwrap(); - let created_session2 = session_context.create(sessions[1].clone()).await; - - assert!(matches!( - created_session2.unwrap_err().sql_err(), - Some(SqlErr::UniqueConstraintViolation(_)) - )); -} - -#[tokio::test] -async fn get_by_id_test() { - let (session_context, session, _, _) = seed_db().await; - - session::Entity::insert(session.clone().into_active_model()) - .exec(&session_context.db_context.get_connection()) - .await - .unwrap(); - - let fetched_session = session_context - .get_by_id(session.id) - .await - .unwrap() - .unwrap(); - - assert_eq!(session, fetched_session); -} - -#[tokio::test] -async fn get_by_non_existing_id_test() { - let (session_context, _, _, _) = seed_db().await; - - let fetched_session = session_context.get_by_id(1).await.unwrap(); - - assert!(fetched_session.is_none()); -} - -#[tokio::test] -async fn get_all_test() { - let (session_context, _, user, _) = seed_db().await; - - let new_sessions = create_sessions(3, user.id); - - session::Entity::insert_many(to_active_models!(new_sessions.clone())) - .exec(&session_context.db_context.get_connection()) - .await - .unwrap(); - - assert_eq!(session_context.get_all().await.unwrap().len(), 3); - - let mut sorted: Vec = new_sessions.clone(); - sorted.sort_by_key(|k| k.id); - - for (i, session) in sorted.into_iter().enumerate() { - assert_eq!(session, new_sessions[i]); - } -} - -#[tokio::test] -async fn get_all_empty_test() { - let (session_context, _, _, _) = seed_db().await; - - let result = session_context.get_all().await.unwrap(); - let empty_accesses: Vec = vec![]; - - assert_eq!(empty_accesses, result); -} - -#[tokio::test] -async fn update_test() { - let (session_context, session, _, _) = seed_db().await; - - session::Entity::insert(session.clone().into_active_model()) - .exec(&session_context.db_context.get_connection()) - .await - .unwrap(); - - //A session has nothing to update - let mut new_session = session::Model { ..session }; - - let mut updated_session = session_context.update(new_session.clone()).await.unwrap(); - - let fetched_session = session::Entity::find_by_id(updated_session.id) - .one(&session_context.db_context.get_connection()) - .await - .unwrap() - .unwrap(); - - new_session.updated_at = fetched_session.updated_at; - updated_session.updated_at = fetched_session.updated_at; - - assert_eq!(new_session, updated_session); - assert_eq!(updated_session, fetched_session); -} - -#[tokio::test] -async fn update_does_not_modify_id_test() { - let (session_context, session, _, _) = seed_db().await; - session::Entity::insert(session.clone().into_active_model()) - .exec(&session_context.db_context.get_connection()) - .await - .unwrap(); - - let updated_session = session::Model { - id: &session.id + 1, - ..session.clone() - }; - let res = session_context.update(updated_session.clone()).await; - - assert!(matches!(res.unwrap_err(), DbErr::RecordNotUpdated)); -} - -#[tokio::test] -async fn update_does_modifies_updated_at_automatically_test() { - let (session_context, mut session, _, _) = seed_db().await; - session::Entity::insert(session.clone().into_active_model()) - .exec(&session_context.db_context.get_connection()) - .await - .unwrap(); - - let updated_session = session::Model { - updated_at: session.clone().updated_at.add(Duration::seconds(1)), - ..session.clone() - }; - let res = session_context - .update(updated_session.clone()) - .await - .unwrap(); - - assert!(session.updated_at < res.updated_at); - - session.updated_at = res.updated_at; - - assert_eq!(session, res); -} - -#[tokio::test] -async fn update_does_not_modify_user_id_test() { - let (session_context, mut session, _, _) = seed_db().await; - session::Entity::insert(session.clone().into_active_model()) - .exec(&session_context.db_context.get_connection()) - .await - .unwrap(); - - let updated_session = session::Model { - user_id: &session.user_id + 1, - ..session.clone() - }; - let res = session_context - .update(updated_session.clone()) - .await - .unwrap(); - - session.updated_at = res.updated_at; - - assert_eq!(session, res); -} - -#[tokio::test] -async fn update_non_existing_id_test() { - let (session_context, session, _, _) = seed_db().await; - - let updated_session = session_context.update(session.clone()).await; - - assert!(matches!( - updated_session.unwrap_err(), - DbErr::RecordNotUpdated - )); -} - -#[tokio::test] -async fn delete_test() { - let (session_context, session, _, _) = seed_db().await; - - session::Entity::insert(session.clone().into_active_model()) - .exec(&session_context.db_context.get_connection()) - .await - .unwrap(); - - let deleted_session = session_context.delete(session.id).await.unwrap(); - - let all_sessions = session::Entity::find() - .all(&session_context.db_context.get_connection()) - .await - .unwrap(); - - assert_eq!(session, deleted_session); - assert!(all_sessions.is_empty()); -} - -#[tokio::test] -async fn delete_cascade_in_use_test() { - let (session_context, session, _, project) = seed_db().await; - - let in_use = create_in_uses(1, project.id, session.id)[0].clone(); - - session::Entity::insert(session.clone().into_active_model()) - .exec(&session_context.db_context.get_connection()) - .await - .unwrap(); - in_use::Entity::insert(in_use.clone().into_active_model()) - .exec(&session_context.db_context.get_connection()) - .await - .unwrap(); - - session_context.delete(session.id).await.unwrap(); - - let all_sessions = session::Entity::find() - .all(&session_context.db_context.get_connection()) - .await - .unwrap(); - let all_in_uses = in_use::Entity::find() - .all(&session_context.db_context.get_connection()) - .await - .unwrap(); - - assert_eq!(all_sessions.len(), 0); - assert_eq!(all_in_uses.len(), 0); -} - -#[tokio::test] -async fn delete_non_existing_id_test() { - let (session_context, _, _, _) = seed_db().await; - - let deleted_session = session_context.delete(1).await; - - assert!(matches!( - deleted_session.unwrap_err(), - DbErr::RecordNotFound(_) - )); -} - -#[tokio::test] -async fn get_by_token_refresh_test() { - let (session_context, session, _, _) = seed_db().await; - - session::Entity::insert(session.clone().into_active_model()) - .exec(&session_context.db_context.get_connection()) - .await - .unwrap(); - - let fetched_session = session_context - .get_by_token(TokenType::RefreshToken, session.refresh_token.clone()) - .await - .unwrap(); - - assert_eq!( - fetched_session.unwrap().refresh_token, - session.refresh_token - ); -} - -#[tokio::test] -async fn get_by_token_access_test() { - let (session_context, session, _, _) = seed_db().await; - - session::Entity::insert(session.clone().into_active_model()) - .exec(&session_context.db_context.get_connection()) - .await - .unwrap(); - - let fetched_session = session_context - .get_by_token(TokenType::AccessToken, session.access_token.clone()) - .await - .unwrap(); - - assert_eq!(fetched_session.unwrap().access_token, session.access_token); -} - -#[tokio::test] -async fn delete_by_token_refresh_test() { - let (session_context, session, _, _) = seed_db().await; - - session::Entity::insert(session.clone().into_active_model()) - .exec(&session_context.db_context.get_connection()) - .await - .unwrap(); - - session_context - .delete_by_token(TokenType::RefreshToken, session.refresh_token.clone()) - .await - .unwrap(); - - let fetched_session = session_context - .get_by_token(TokenType::RefreshToken, session.refresh_token.clone()) - .await - .unwrap(); - - assert!(fetched_session.is_none()); -} - -#[tokio::test] -async fn delete_by_token_access_test() { - let (session_context, session, _, _) = seed_db().await; - - session::Entity::insert(session.clone().into_active_model()) - .exec(&session_context.db_context.get_connection()) - .await - .unwrap(); - - session_context - .delete_by_token(TokenType::AccessToken, session.access_token.clone()) - .await - .unwrap(); - - let fetched_session = session_context - .get_by_token(TokenType::AccessToken, session.access_token.clone()) - .await - .unwrap(); - - assert!(fetched_session.is_none()); -} diff --git a/src/tests/contexts/user_context.rs b/src/tests/contexts/user_context.rs deleted file mode 100644 index 96cadab..0000000 --- a/src/tests/contexts/user_context.rs +++ /dev/null @@ -1,531 +0,0 @@ -use crate::tests::contexts::helpers::*; -use crate::{ - contexts::context_impls::UserContext, - contexts::context_traits::{EntityContextTrait, UserContextTrait}, - entities::{access, project, session, user}, - to_active_models, -}; -use sea_orm::{entity::prelude::*, IntoActiveModel}; -use std::matches; - -async fn seed_db() -> (UserContext, user::Model) { - let db_context = get_reset_database_context().await; - - let user_context = UserContext::new(db_context); - - let user = create_users(1)[0].clone(); - - (user_context, user) -} - -// Test the functionality of the 'create' function, which creates a user in the contexts -#[tokio::test] -async fn create_test() { - // Setting up contexts and user context - let (user_context, user) = seed_db().await; - - // Creates the user in the contexts using the 'create' function - let created_user = user_context.create(user.clone()).await.unwrap(); - - let fetched_user = user::Entity::find_by_id(created_user.id) - .one(&user_context.db_context.get_connection()) - .await - .unwrap() - .unwrap(); - - // Assert if the new_user, created_user, and fetched_user are the same - assert_eq!(user, created_user); - assert_eq!(created_user, fetched_user); -} - -#[tokio::test] -async fn create_non_unique_username_test() { - // Setting up contexts and user context - let (user_context, user) = seed_db().await; - - // Creates a model of the user which will be created - let mut users = create_users(2); - - users[0].username = user.clone().username; - users[1].username = user.clone().username; - - // Creates the user in the contexts using the 'create' function - let _created_user1 = user_context.create(users[0].clone()).await.unwrap(); - let created_user2 = user_context.create(users[1].clone()).await; - - assert!(matches!( - created_user2.unwrap_err().sql_err(), - Some(SqlErr::UniqueConstraintViolation(_)) - )); -} - -#[tokio::test] -async fn create_non_unique_email_test() { - // Setting up contexts and user context - let (user_context, user) = seed_db().await; - - // Creates a model of the user which will be created - let mut users = create_users(2); - - users[0].email = user.clone().email; - users[1].email = user.clone().email; - - // Creates the user in the contexts using the 'create' function - let _created_user1 = user_context.create(users[0].clone()).await.unwrap(); - let created_user2 = user_context.create(users[1].clone()).await; - - // Assert if the new_user, created_user, and fetched_user are the same - assert!(matches!( - created_user2.unwrap_err().sql_err(), - Some(SqlErr::UniqueConstraintViolation(_)) - )); -} - -#[tokio::test] -async fn create_auto_increment_test() { - // Setting up contexts and user context - let (user_context, user) = seed_db().await; - - let mut users = create_users(2); - - users[0].id = user.clone().id; - users[1].id = user.clone().id; - - // Creates the user in the contexts using the 'create' function - let created_user1 = user_context.create(users[0].clone()).await.unwrap(); - let created_user2 = user_context.create(users[1].clone()).await.unwrap(); - - let fetched_user1 = user::Entity::find_by_id(created_user1.id) - .one(&user_context.db_context.get_connection()) - .await - .unwrap() - .unwrap(); - - let fetched_user2 = user::Entity::find_by_id(created_user2.id) - .one(&user_context.db_context.get_connection()) - .await - .unwrap() - .unwrap(); - - // Assert if the new_user, created_user, and fetched_user are the same - assert_ne!(fetched_user1.id, fetched_user2.id); - assert_ne!(created_user1.id, created_user2.id); - assert_eq!(created_user1.id, fetched_user1.id); - assert_eq!(created_user2.id, fetched_user2.id); -} - -#[tokio::test] -async fn get_by_id_test() { - // Setting up contexts and user context - let (user_context, user) = seed_db().await; - - // Creates the user in the contexts using the 'create' function - user::Entity::insert(user.clone().into_active_model()) - .exec(&user_context.db_context.get_connection()) - .await - .unwrap(); - - // Fetches the user created using the 'get_by_id' function - let fetched_user = user_context.get_by_id(user.id).await.unwrap().unwrap(); - - // Assert if the new_user, created_user, and fetched_user are the same - assert_eq!(user, fetched_user); -} - -#[tokio::test] -async fn get_by_non_existing_id_test() { - // Setting up contexts and user context - let (user_context, _) = seed_db().await; - - // Fetches the user created using the 'get_by_id' function - let fetched_user = user_context.get_by_id(1).await.unwrap(); - - assert!(fetched_user.is_none()); -} - -#[tokio::test] -async fn get_all_test() { - // Setting up contexts and user context - let (user_context, _) = seed_db().await; - - let users = create_users(10); - let active_users_vec = to_active_models!(users.clone()); - - user::Entity::insert_many(active_users_vec) - .exec(&user_context.db_context.get_connection()) - .await - .unwrap(); - - assert_eq!(user_context.get_all().await.unwrap().len(), 10); - - let mut sorted = users.clone(); - sorted.sort_by_key(|k| k.id); - - for (i, user) in sorted.into_iter().enumerate() { - assert_eq!(user, users[i]); - } -} - -#[tokio::test] -async fn get_all_empty_test() { - // Setting up contexts and user context - let (user_context, _) = seed_db().await; - - let result = user_context.get_all().await.unwrap(); - let empty_users: Vec = vec![]; - - assert_eq!(empty_users, result); -} - -#[tokio::test] -async fn update_test() { - // Setting up contexts and user context - let (user_context, user) = seed_db().await; - - user::Entity::insert(user.clone().into_active_model()) - .exec(&user_context.db_context.get_connection()) - .await - .unwrap(); - - let new_user = user::Model { ..user }; - - let updated_user = user_context.update(new_user.clone()).await.unwrap(); - - let fetched_user = user::Entity::find_by_id(updated_user.id) - .one(&user_context.db_context.get_connection()) - .await - .unwrap() - .unwrap(); - - assert_eq!(new_user, updated_user); - assert_eq!(updated_user, fetched_user); -} - -#[tokio::test] -async fn update_modifies_username_test() { - let (user_context, user) = seed_db().await; - - let user = user::Model { - username: "tester1".into(), - ..user.clone() - }; - - user::Entity::insert(user.clone().into_active_model()) - .exec(&user_context.db_context.get_connection()) - .await - .unwrap(); - - let new_user = user::Model { - username: "tester2".into(), - ..user.clone() - }; - - let updated_user = user_context.update(new_user.clone()).await.unwrap(); - - assert_ne!(user, updated_user); - assert_ne!(user, new_user); -} - -#[tokio::test] -async fn update_modifies_email_test() { - let (user_context, user) = seed_db().await; - - let user = user::Model { - email: "tester1@mail.dk".into(), - ..user.clone() - }; - - user::Entity::insert(user.clone().into_active_model()) - .exec(&user_context.db_context.get_connection()) - .await - .unwrap(); - - let new_user = user::Model { - email: "tester2@mail.dk".into(), - ..user.clone() - }; - - let updated_user = user_context.update(new_user.clone()).await.unwrap(); - - assert_ne!(user, updated_user); - assert_ne!(user, new_user); -} - -#[tokio::test] -async fn update_modifies_password_test() { - let (user_context, user) = seed_db().await; - - let user = user::Model { - password: "12345".into(), - ..user.clone() - }; - - user::Entity::insert(user.clone().into_active_model()) - .exec(&user_context.db_context.get_connection()) - .await - .unwrap(); - - let new_user = user::Model { - password: "123456".into(), - ..user.clone() - }; - - let updated_user = user_context.update(new_user.clone()).await.unwrap(); - - assert_ne!(user, updated_user); - assert_ne!(user, new_user); -} - -#[tokio::test] -async fn update_does_not_modify_id_test() { - let (user_context, user) = seed_db().await; - - user::Entity::insert(user.clone().into_active_model()) - .exec(&user_context.db_context.get_connection()) - .await - .unwrap(); - - let updated_user = user::Model { - id: user.id + 1, - ..user - }; - - let res = user_context.update(updated_user.clone()).await; - - assert!(matches!(res.unwrap_err(), DbErr::RecordNotUpdated)); -} - -#[tokio::test] -async fn update_non_unique_username_test() { - // Setting up contexts and user context - let (user_context, _) = seed_db().await; - - let users = create_users(2); - - user::Entity::insert_many(to_active_models!(users.clone())) - .exec(&user_context.db_context.get_connection()) - .await - .unwrap(); - - let new_user = user::Model { - username: users[1].clone().username, - ..users[0].clone() - }; - - let updated_user = user_context.update(new_user.clone()).await; - - // Assert if the new_user, created_user, and fetched_user are the same - assert!(matches!( - updated_user.unwrap_err().sql_err(), - Some(SqlErr::UniqueConstraintViolation(_)) - )); -} - -#[tokio::test] -async fn update_non_unique_email_test() { - // Setting up contexts and user context - let (user_context, _) = seed_db().await; - - // Creates a model of the user which will be created - let users = create_users(2); - - user::Entity::insert_many(to_active_models!(users.clone())) - .exec(&user_context.db_context.get_connection()) - .await - .unwrap(); - - let new_user = user::Model { - email: users[1].clone().email, - ..users[0].clone() - }; - - let updated_user = user_context.update(new_user.clone()).await; - - // Assert if the new_user, created_user, and fetched_user are the same - assert!(matches!( - updated_user.unwrap_err().sql_err(), - Some(SqlErr::UniqueConstraintViolation(_)) - )); -} - -#[tokio::test] -async fn update_non_existing_id_test() { - // Setting up contexts and user context - let (user_context, user) = seed_db().await; - - let updated_user = user_context.update(user.clone()).await; - - // Assert if the new_user, created_user, and fetched_user are the same - assert!(matches!(updated_user.unwrap_err(), DbErr::RecordNotUpdated)); -} - -#[tokio::test] -async fn delete_test() { - // Setting up contexts and user context - let (user_context, user) = seed_db().await; - - user::Entity::insert(user.clone().into_active_model()) - .exec(&user_context.db_context.get_connection()) - .await - .unwrap(); - - let deleted_user = user_context.delete(user.id).await.unwrap(); - - let all_users = user::Entity::find() - .all(&user_context.db_context.get_connection()) - .await - .unwrap(); - - assert_eq!(user, deleted_user); - assert!(all_users.is_empty()); -} - -#[tokio::test] -async fn delete_cascade_project_test() { - // Setting up contexts and user context - let (user_context, user) = seed_db().await; - - let project = create_projects(1, user.clone().id)[0].clone(); - - user::Entity::insert(user.clone().into_active_model()) - .exec(&user_context.db_context.get_connection()) - .await - .unwrap(); - project::Entity::insert(project.clone().into_active_model()) - .exec(&user_context.db_context.get_connection()) - .await - .unwrap(); - - user_context.delete(user.id).await.unwrap(); - - let all_users = user::Entity::find() - .all(&user_context.db_context.get_connection()) - .await - .unwrap(); - let all_projects = project::Entity::find() - .all(&user_context.db_context.get_connection()) - .await - .unwrap(); - - assert_eq!(all_users.len(), 0); - assert_eq!(all_projects.len(), 0); -} - -#[tokio::test] -async fn delete_cascade_access_test() { - // Setting up contexts and user context - let (user_context, user) = seed_db().await; - - let project = create_projects(1, user.clone().id)[0].clone(); - let access = create_accesses(1, user.clone().id, project.clone().id)[0].clone(); - - user::Entity::insert(user.clone().into_active_model()) - .exec(&user_context.db_context.get_connection()) - .await - .unwrap(); - project::Entity::insert(project.clone().into_active_model()) - .exec(&user_context.db_context.get_connection()) - .await - .unwrap(); - access::Entity::insert(access.clone().into_active_model()) - .exec(&user_context.db_context.get_connection()) - .await - .unwrap(); - - user_context.delete(user.id).await.unwrap(); - - let all_users = user::Entity::find() - .all(&user_context.db_context.get_connection()) - .await - .unwrap(); - let all_projects = project::Entity::find() - .all(&user_context.db_context.get_connection()) - .await - .unwrap(); - let all_accesses = access::Entity::find() - .all(&user_context.db_context.get_connection()) - .await - .unwrap(); - - assert_eq!(all_users.len(), 0); - assert_eq!(all_projects.len(), 0); - assert_eq!(all_accesses.len(), 0); -} - -#[tokio::test] -async fn delete_cascade_session_test() { - // Setting up contexts and user context - let (user_context, user) = seed_db().await; - - let session = create_sessions(1, user.clone().id)[0].clone(); - - user::Entity::insert(user.clone().into_active_model()) - .exec(&user_context.db_context.get_connection()) - .await - .unwrap(); - session::Entity::insert(session.clone().into_active_model()) - .exec(&user_context.db_context.get_connection()) - .await - .unwrap(); - - user_context.delete(user.id).await.unwrap(); - - let all_users = user::Entity::find() - .all(&user_context.db_context.get_connection()) - .await - .unwrap(); - let all_sessions = session::Entity::find() - .all(&user_context.db_context.get_connection()) - .await - .unwrap(); - - assert_eq!(all_users.len(), 0); - assert_eq!(all_sessions.len(), 0); -} - -#[tokio::test] -async fn delete_non_existing_id_test() { - // Setting up contexts and user context - let (user_context, _) = seed_db().await; - - let deleted_user = user_context.delete(1).await; - - // Assert if the new_user, created_user, and fetched_user are the same - assert!(matches!( - deleted_user.unwrap_err(), - DbErr::RecordNotFound(_) - )); -} - -#[tokio::test] -async fn get_by_username_test() { - let (user_context, user) = seed_db().await; - - user::Entity::insert(user.clone().into_active_model()) - .exec(&user_context.db_context.get_connection()) - .await - .unwrap(); - - // Fetches the user created using the 'get_by_username' function - let fetched_user = user_context - .get_by_username(user.username.clone()) - .await - .unwrap(); - - // Assert if the fetched user is the same as the created user - assert_eq!(fetched_user.unwrap().username, user.username); -} - -#[tokio::test] -async fn get_by_email_test() { - let (user_context, user) = seed_db().await; - - user::Entity::insert(user.clone().into_active_model()) - .exec(&user_context.db_context.get_connection()) - .await - .unwrap(); - - let fetched_user = user_context.get_by_email(user.email.clone()).await.unwrap(); - - assert_eq!(fetched_user.unwrap().email, user.email); -} diff --git a/src/tests/controllers/access_controller.rs b/src/tests/controllers/access_controller.rs deleted file mode 100644 index 46fbe2d..0000000 --- a/src/tests/controllers/access_controller.rs +++ /dev/null @@ -1,523 +0,0 @@ -use crate::api::server::protobuf::create_access_request::User; -use crate::api::server::protobuf::{ - AccessInfo, CreateAccessRequest, DeleteAccessRequest, ListAccessInfoRequest, - UpdateAccessRequest, -}; -use crate::controllers::controller_impls::AccessController; -use crate::controllers::controller_traits::AccessControllerTrait; -use crate::entities::{access, project, user}; -use crate::tests::controllers::helpers::{disguise_context_mocks, get_mock_contexts}; -use mockall::predicate; -use sea_orm::DbErr; -use std::str::FromStr; -use tonic::{metadata, Code, Request}; - -#[tokio::test] -async fn create_invalid_access_returns_err() { - let mut mock_contexts = get_mock_contexts(); - - let access = access::Model { - id: Default::default(), - role: "Editor".to_string(), - project_id: 1, - user_id: 1, - }; - - mock_contexts - .access_context_mock - .expect_create() - .with(predicate::eq(access.clone())) - .returning(move |_| Err(DbErr::RecordNotInserted)); - - mock_contexts - .access_context_mock - .expect_get_access_by_uid_and_project_id() - .with(predicate::eq(1), predicate::eq(1)) - .returning(move |_, _| { - Ok(Some(access::Model { - id: Default::default(), - role: "Editor".to_owned(), - user_id: 1, - project_id: 1, - })) - }); - - mock_contexts - .user_context_mock - .expect_get_by_id() - .with(predicate::eq(1)) - .returning(move |_| { - Ok(Some(user::Model { - id: 1, - email: Default::default(), - username: "test".to_string(), - password: "test".to_string(), - })) - }); - - let mut request = Request::new(CreateAccessRequest { - role: "Editor".to_string(), - project_id: 1, - user: Some(User::UserId(1)), - }); - - request.metadata_mut().insert( - "uid", - tonic::metadata::MetadataValue::from_str("1").unwrap(), - ); - - let contexts = disguise_context_mocks(mock_contexts); - let access_logic = AccessController::new(contexts); - - let res = access_logic.create_access(request).await.unwrap_err(); - - assert_eq!(res.code(), Code::Internal); -} - -#[tokio::test] -async fn create_access_returns_ok() { - let mut mock_contexts = get_mock_contexts(); - - let access = access::Model { - id: Default::default(), - role: "Editor".to_string(), - project_id: 1, - user_id: 1, - }; - - mock_contexts - .access_context_mock - .expect_get_access_by_uid_and_project_id() - .with(predicate::eq(1), predicate::eq(1)) - .returning(move |_, _| { - Ok(Some(access::Model { - id: Default::default(), - role: "Editor".to_string(), - user_id: 1, - project_id: 1, - })) - }); - - mock_contexts - .access_context_mock - .expect_create() - .with(predicate::eq(access.clone())) - .returning(move |_| Ok(access.clone())); - - mock_contexts - .user_context_mock - .expect_get_by_id() - .with(predicate::eq(1)) - .returning(move |_| { - Ok(Some(user::Model { - id: 1, - email: Default::default(), - username: "test".to_string(), - password: "test".to_string(), - })) - }); - - let mut request = Request::new(CreateAccessRequest { - role: "Editor".to_string(), - project_id: 1, - user: Some(User::UserId(1)), - }); - - request.metadata_mut().insert( - "uid", - tonic::metadata::MetadataValue::from_str("1").unwrap(), - ); - - let contexts = disguise_context_mocks(mock_contexts); - let access_logic = AccessController::new(contexts); - - let res = access_logic.create_access(request).await; - - assert!(res.is_ok()); -} - -#[tokio::test] -async fn update_invalid_access_returns_err() { - let mut mock_contexts = get_mock_contexts(); - - let access = access::Model { - id: 2, - role: "Editor".to_string(), - project_id: Default::default(), - user_id: Default::default(), - }; - - mock_contexts - .access_context_mock - .expect_update() - .with(predicate::eq(access.clone())) - .returning(move |_| Err(DbErr::RecordNotUpdated)); - - mock_contexts - .access_context_mock - .expect_get_by_id() - .with(predicate::eq(2)) - .returning(move |_| { - Ok(Some(access::Model { - id: 1, - role: "Editor".to_string(), - project_id: 1, - user_id: 2, - })) - }); - - mock_contexts - .access_context_mock - .expect_get_access_by_uid_and_project_id() - .with(predicate::eq(1), predicate::eq(1)) - .returning(move |_, _| { - Ok(Some(access::Model { - id: 1, - role: "Editor".to_string(), - project_id: 1, - user_id: 1, - })) - }); - - mock_contexts - .project_context_mock - .expect_get_by_id() - .with(predicate::eq(1)) - .returning(move |_| { - Ok(Some(project::Model { - id: 1, - name: "test".to_string(), - owner_id: 1, - components_info: Default::default(), - })) - }); - - let mut request = Request::new(UpdateAccessRequest { - id: 2, - role: "Editor".to_string(), - }); - - request.metadata_mut().insert( - "uid", - tonic::metadata::MetadataValue::from_str("1").unwrap(), - ); - - let contexts = disguise_context_mocks(mock_contexts); - let access_logic = AccessController::new(contexts); - - let res = access_logic.update_access(request).await.unwrap_err(); - - assert_eq!(res.code(), Code::Internal); -} - -#[tokio::test] -async fn update_access_returns_ok() { - let mut mock_contexts = get_mock_contexts(); - - let access = access::Model { - id: 2, - role: "Editor".to_string(), - project_id: Default::default(), - user_id: Default::default(), - }; - - mock_contexts - .access_context_mock - .expect_update() - .with(predicate::eq(access.clone())) - .returning(move |_| Ok(access.clone())); - - mock_contexts - .access_context_mock - .expect_get_by_id() - .with(predicate::eq(2)) - .returning(move |_| { - Ok(Some(access::Model { - id: 1, - role: "Editor".to_string(), - project_id: 1, - user_id: 2, - })) - }); - - mock_contexts - .access_context_mock - .expect_get_access_by_uid_and_project_id() - .with(predicate::eq(1), predicate::eq(1)) - .returning(move |_, _| { - Ok(Some(access::Model { - id: 1, - role: "Editor".to_string(), - project_id: 1, - user_id: 1, - })) - }); - - mock_contexts - .project_context_mock - .expect_get_by_id() - .with(predicate::eq(1)) - .returning(move |_| { - Ok(Some(project::Model { - id: 1, - name: "test".to_string(), - owner_id: 1, - components_info: Default::default(), - })) - }); - - let mut request = Request::new(UpdateAccessRequest { - id: 2, - role: "Editor".to_string(), - }); - - request.metadata_mut().insert( - "uid", - tonic::metadata::MetadataValue::from_str("1").unwrap(), - ); - - let contexts = disguise_context_mocks(mock_contexts); - let access_logic = AccessController::new(contexts); - - let res = access_logic.update_access(request).await; - - print!("{:?}", res); - - assert!(res.is_ok()); -} - -#[tokio::test] -async fn delete_invalid_access_returns_err() { - let mut mock_contexts = get_mock_contexts(); - - mock_contexts - .access_context_mock - .expect_delete() - .with(predicate::eq(2)) - .returning(move |_| Err(DbErr::RecordNotFound("".to_string()))); - - mock_contexts - .access_context_mock - .expect_get_by_id() - .with(predicate::eq(2)) - .returning(move |_| { - Ok(Some(access::Model { - id: 1, - role: "Editor".to_string(), - project_id: 1, - user_id: 2, - })) - }); - - mock_contexts - .access_context_mock - .expect_get_access_by_uid_and_project_id() - .with(predicate::eq(1), predicate::eq(1)) - .returning(move |_, _| { - Ok(Some(access::Model { - id: 1, - role: "Editor".to_string(), - project_id: 1, - user_id: 1, - })) - }); - - mock_contexts - .project_context_mock - .expect_get_by_id() - .with(predicate::eq(1)) - .returning(move |_| { - Ok(Some(project::Model { - id: 1, - name: "test".to_string(), - owner_id: 1, - components_info: Default::default(), - })) - }); - - let mut request = Request::new(DeleteAccessRequest { id: 2 }); - - request.metadata_mut().insert( - "uid", - tonic::metadata::MetadataValue::from_str("1").unwrap(), - ); - - let contexts = disguise_context_mocks(mock_contexts); - let access_logic = AccessController::new(contexts); - - let res = access_logic.delete_access(request).await.unwrap_err(); - - assert_eq!(res.code(), Code::NotFound); -} - -#[tokio::test] -async fn delete_access_returns_ok() { - let mut mock_contexts = get_mock_contexts(); - - let access = access::Model { - id: 2, - role: "Editor".to_string(), - project_id: Default::default(), - user_id: Default::default(), - }; - - mock_contexts - .access_context_mock - .expect_delete() - .with(predicate::eq(2)) - .returning(move |_| Ok(access.clone())); - - mock_contexts - .access_context_mock - .expect_get_by_id() - .with(predicate::eq(2)) - .returning(move |_| { - Ok(Some(access::Model { - id: 1, - role: "Editor".to_string(), - project_id: 1, - user_id: 2, - })) - }); - - mock_contexts - .access_context_mock - .expect_get_access_by_uid_and_project_id() - .with(predicate::eq(1), predicate::eq(1)) - .returning(move |_, _| { - Ok(Some(access::Model { - id: 1, - role: "Editor".to_string(), - project_id: 1, - user_id: 1, - })) - }); - - mock_contexts - .project_context_mock - .expect_get_by_id() - .with(predicate::eq(1)) - .returning(move |_| { - Ok(Some(project::Model { - id: 1, - name: "test".to_string(), - owner_id: 1, - components_info: Default::default(), - })) - }); - - let mut request = Request::new(DeleteAccessRequest { id: 2 }); - - request.metadata_mut().insert( - "uid", - tonic::metadata::MetadataValue::from_str("1").unwrap(), - ); - - let contexts = disguise_context_mocks(mock_contexts); - let access_logic = AccessController::new(contexts); - - let res = access_logic.delete_access(request).await; - - assert!(res.is_ok()); -} - -#[tokio::test] -async fn list_access_info_returns_ok() { - let mut mock_contexts = get_mock_contexts(); - - let mut request: Request = - Request::new(ListAccessInfoRequest { project_id: 1 }); - - request - .metadata_mut() - .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); - - let access = AccessInfo { - id: 1, - role: "Editor".to_string(), - project_id: 1, - user_id: 1, - }; - - mock_contexts - .access_context_mock - .expect_get_access_by_uid_and_project_id() - .returning(move |_, _| { - Ok(Some(access::Model { - id: 1, - role: "Editor".to_string(), - project_id: Default::default(), - user_id: Default::default(), - })) - }); - - mock_contexts - .access_context_mock - .expect_get_access_by_project_id() - .returning(move |_| Ok(vec![access.clone()])); - - let contexts = disguise_context_mocks(mock_contexts); - let access_logic = AccessController::new(contexts); - - let res = access_logic.list_access_info(request).await; - - assert!(res.is_ok()); -} - -#[tokio::test] -async fn list_access_info_returns_not_found() { - let mut mock_contexts = get_mock_contexts(); - - let mut request = Request::new(ListAccessInfoRequest { project_id: 1 }); - - request - .metadata_mut() - .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); - - let access = access::Model { - id: 1, - role: "Editor".to_string(), - project_id: 1, - user_id: 1, - }; - - mock_contexts - .access_context_mock - .expect_get_access_by_project_id() - .returning(move |_| Ok(vec![])); - - mock_contexts - .access_context_mock - .expect_get_access_by_uid_and_project_id() - .returning(move |_, _| Ok(Some(access.clone()))); - - let contexts = disguise_context_mocks(mock_contexts); - let access_logic = AccessController::new(contexts); - - let res = access_logic.list_access_info(request).await.unwrap_err(); - - assert_eq!(res.code(), Code::NotFound); -} - -#[tokio::test] -async fn list_access_info_returns_no_permission() { - let mut request = Request::new(ListAccessInfoRequest { project_id: 1 }); - - request - .metadata_mut() - .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); - - let mut mock_contexts = get_mock_contexts(); - - mock_contexts - .access_context_mock - .expect_get_access_by_uid_and_project_id() - .returning(move |_, _| Ok(None)); - - let contexts = disguise_context_mocks(mock_contexts); - let access_logic = AccessController::new(contexts); - - let res = access_logic.list_access_info(request).await.unwrap_err(); - - assert_eq!(res.code(), Code::PermissionDenied); -} diff --git a/src/tests/controllers/helpers.rs b/src/tests/controllers/helpers.rs deleted file mode 100644 index 49543c3..0000000 --- a/src/tests/controllers/helpers.rs +++ /dev/null @@ -1,194 +0,0 @@ -#![cfg(test)] - -use crate::api::auth::TokenType; -use crate::api::server::protobuf::AccessInfo; -use crate::api::server::protobuf::ProjectInfo; -use crate::api::server::protobuf::{ - QueryRequest, QueryResponse, SimulationStartRequest, SimulationStepRequest, - SimulationStepResponse, UserTokenResponse, -}; -use crate::contexts::context_collection::ContextCollection; -use crate::contexts::context_traits::*; -use crate::entities::{access, in_use, project, query, session, user}; -use crate::services::service_collection::ServiceCollection; -use crate::services::service_traits::*; -use async_trait::async_trait; -use mockall::mock; -use sea_orm::DbErr; -use std::sync::Arc; -use tonic::{Request, Response, Status}; - -pub fn get_mock_contexts() -> MockContexts { - MockContexts { - access_context_mock: MockAccessContext::new(), - in_use_context_mock: MockInUseContext::new(), - project_context_mock: MockProjectContext::new(), - query_context_mock: MockQueryContext::new(), - session_context_mock: MockSessionContext::new(), - user_context_mock: MockUserContext::new(), - } -} - -pub fn get_mock_services() -> MockServices { - MockServices { - hashing_service_mock: MockHashingService::new(), - reveaal_service_mock: MockReveaalService::new(), - } -} - -pub fn disguise_context_mocks(mock_services: MockContexts) -> ContextCollection { - ContextCollection { - access_context: Arc::new(mock_services.access_context_mock), - in_use_context: Arc::new(mock_services.in_use_context_mock), - project_context: Arc::new(mock_services.project_context_mock), - query_context: Arc::new(mock_services.query_context_mock), - session_context: Arc::new(mock_services.session_context_mock), - user_context: Arc::new(mock_services.user_context_mock), - } -} - -pub fn disguise_service_mocks(mock_services: MockServices) -> ServiceCollection { - ServiceCollection { - hashing_service: Arc::new(mock_services.hashing_service_mock), - reveaal_service: Arc::new(mock_services.reveaal_service_mock), - } -} - -pub struct MockContexts { - pub(crate) access_context_mock: MockAccessContext, - pub(crate) in_use_context_mock: MockInUseContext, - pub(crate) project_context_mock: MockProjectContext, - pub(crate) query_context_mock: MockQueryContext, - pub(crate) session_context_mock: MockSessionContext, - pub(crate) user_context_mock: MockUserContext, -} - -pub struct MockServices { - pub(crate) hashing_service_mock: MockHashingService, - pub(crate) reveaal_service_mock: MockReveaalService, -} - -mock! { - pub AccessContext {} - #[async_trait] - impl EntityContextTrait for AccessContext { - async fn create(&self, entity: access::Model) -> Result; - async fn get_by_id(&self, entity_id: i32) -> Result, DbErr>; - async fn get_all(&self) -> Result, DbErr>; - async fn update(&self, entity: access::Model) -> Result; - async fn delete(&self, entity_id: i32) -> Result; - } - #[async_trait] - impl AccessContextTrait for AccessContext { - async fn get_access_by_uid_and_project_id( - &self, - uid: i32, - project_id: i32, - ) -> Result, DbErr>; - - async fn get_access_by_project_id( - &self, - project_id: i32, - ) -> Result, DbErr>; - } -} - -mock! { - pub InUseContext {} - #[async_trait] - impl EntityContextTrait for InUseContext { - async fn create(&self, entity: in_use::Model) -> Result; - async fn get_by_id(&self, entity_id: i32) -> Result, DbErr>; - async fn get_all(&self) -> Result, DbErr>; - async fn update(&self, entity: in_use::Model) -> Result; - async fn delete(&self, entity_id: i32) -> Result; - } - #[async_trait] - impl InUseContextTrait for InUseContext {} -} - -mock! { - pub ProjectContext {} - #[async_trait] - impl EntityContextTrait for ProjectContext { - async fn create(&self, entity: project::Model) -> Result; - async fn get_by_id(&self, entity_id: i32) -> Result, DbErr>; - async fn get_all(&self) -> Result, DbErr>; - async fn update(&self, entity: project::Model) -> Result; - async fn delete(&self, entity_id: i32) -> Result; - } - #[async_trait] - impl ProjectContextTrait for ProjectContext { - async fn get_project_info_by_uid(&self, uid: i32) -> Result, DbErr>; - } -} - -mock! { - pub QueryContext {} - #[async_trait] - impl EntityContextTrait for QueryContext { - async fn create(&self, entity: query::Model) -> Result; - async fn get_by_id(&self, entity_id: i32) -> Result, DbErr>; - async fn get_all(&self) -> Result, DbErr>; - async fn update(&self, entity: query::Model) -> Result; - async fn delete(&self, entity_id: i32) -> Result; - } - #[async_trait] - impl QueryContextTrait for QueryContext { - async fn get_all_by_project_id(&self, project_id: i32) -> Result, DbErr>; - } -} - -mock! { - pub SessionContext {} - #[async_trait] - impl EntityContextTrait for SessionContext { - async fn create(&self, entity: session::Model) -> Result; - async fn get_by_id(&self, entity_id: i32) -> Result, DbErr>; - async fn get_all(&self) -> Result, DbErr>; - async fn update(&self, entity: session::Model) -> Result; - async fn delete(&self, entity_id: i32) -> Result; - } - #[async_trait] - impl SessionContextTrait for SessionContext { - async fn get_by_token(&self, token_type: TokenType, token: String) -> Result, DbErr>; - async fn delete_by_token(&self, token_type: TokenType, token: String) -> Result; - } -} - -mock! { - pub UserContext {} - #[async_trait] - impl EntityContextTrait for UserContext { - async fn create(&self, entity: user::Model) -> Result; - async fn get_by_id(&self, entity_id: i32) -> Result, DbErr>; - async fn get_all(&self) -> Result, DbErr>; - async fn update(&self, entity: user::Model) -> Result; - async fn delete(&self, entity_id: i32) -> Result; - } - #[async_trait] - impl UserContextTrait for UserContext { - async fn get_by_username(&self, username: String) -> Result, DbErr>; - async fn get_by_email(&self, email: String) -> Result, DbErr>; - async fn get_by_ids(&self, ids: Vec) -> Result, DbErr>; - } -} - -mock! { - pub ReveaalService{} - #[async_trait] - impl ReveaalServiceTrait for ReveaalService { - async fn get_user_token(&self,request: Request<()>) -> Result, Status>; - async fn send_query(&self,request: Request) -> Result, Status>; - async fn start_simulation(&self, request: Request) -> Result, Status>; - async fn take_simulation_step(&self, request: Request) -> Result, Status>; - } -} - -mock! { - pub HashingService {} - impl HashingServiceTrait for HashingService { - fn hash_password(&self, password: String) -> Result; - fn verify_password(&self, password: String, hash: &str) -> Result; - } -} diff --git a/src/tests/controllers/mod.rs b/src/tests/controllers/mod.rs deleted file mode 100644 index 1630fab..0000000 --- a/src/tests/controllers/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod helpers; diff --git a/src/tests/controllers/project_controller.rs b/src/tests/controllers/project_controller.rs deleted file mode 100644 index eda3df2..0000000 --- a/src/tests/controllers/project_controller.rs +++ /dev/null @@ -1,1519 +0,0 @@ -use crate::controllers::controller_impls::ProjectController; -use crate::controllers::controller_traits::ProjectControllerTrait; -use crate::tests::controllers::helpers::disguise_context_mocks; -use crate::{ - api::{ - auth::TokenType, - server::protobuf::{ - component::Rep, Component, ComponentsInfo, CreateProjectRequest, DeleteProjectRequest, - GetProjectRequest, ProjectInfo, UpdateProjectRequest, - }, - }, - entities::{access, in_use, project, query, session}, - tests::controllers::helpers::get_mock_contexts, -}; -use chrono::Utc; -use mockall::predicate; -use sea_orm::DbErr; -use std::str::FromStr; -use tonic::{metadata, Code, Request}; - -#[tokio::test] -async fn create_project_returns_ok() { - let mut mock_contexts = get_mock_contexts(); - - let uid = 0; - - let components_info = ComponentsInfo { - components: vec![], - components_hash: 0, - }; - - let project = project::Model { - id: Default::default(), - name: Default::default(), - components_info: serde_json::to_value(components_info.clone()).unwrap(), - owner_id: uid, - }; - - let access = access::Model { - id: Default::default(), - role: "Editor".to_string(), - user_id: uid, - project_id: project.id, - }; - - let session = session::Model { - id: Default::default(), - refresh_token: "refresh_token".to_string(), - access_token: "access_token".to_string(), - updated_at: Default::default(), - user_id: uid, - }; - - let in_use = in_use::Model { - project_id: project.id, - session_id: session.id, - latest_activity: Default::default(), - }; - - mock_contexts - .project_context_mock - .expect_create() - .with(predicate::eq(project.clone())) - .returning(move |_| Ok(project.clone())); - - mock_contexts - .access_context_mock - .expect_create() - .with(predicate::eq(access.clone())) - .returning(move |_| Ok(access.clone())); - - mock_contexts - .session_context_mock - .expect_get_by_token() - .with( - predicate::eq(TokenType::AccessToken), - predicate::eq("access_token".to_string()), - ) - .returning(move |_, _| Ok(Some(session.clone()))); - - mock_contexts - .in_use_context_mock - .expect_create() - .with(predicate::eq(in_use.clone())) - .returning(move |_| Ok(in_use.clone())); - - let mut request = Request::new(CreateProjectRequest { - name: Default::default(), - components_info: Option::from(components_info), - }); - - request - .metadata_mut() - .insert("uid", uid.to_string().parse().unwrap()); - - request.metadata_mut().insert( - "authorization", - metadata::MetadataValue::from_str("Bearer access_token").unwrap(), - ); - - let contexts = disguise_context_mocks(mock_contexts); - let project_logic = ProjectController::new(contexts); - - let res = project_logic.create_project(request).await; - - assert!(res.is_ok()); -} - -#[tokio::test] -async fn create_project_existing_name_returns_err() { - let mut mock_contexts = get_mock_contexts(); - - let uid = 0; - - let project = project::Model { - id: Default::default(), - name: "project".to_string(), - components_info: Default::default(), - owner_id: uid, - }; - - mock_contexts - .project_context_mock - .expect_create() - .with(predicate::eq(project.clone())) - .returning(move |_| Err(DbErr::RecordNotInserted)); //todo!("Needs to be a SqlError with UniqueConstraintViolation with 'name' in message) - - let mut request = Request::new(CreateProjectRequest { - name: "project".to_string(), - components_info: Default::default(), - }); - - request - .metadata_mut() - .insert("uid", uid.to_string().parse().unwrap()); - - let contexts = disguise_context_mocks(mock_contexts); - let project_logic = ProjectController::new(contexts); - - let res = project_logic.create_project(request).await; - - assert_eq!(res.unwrap_err().code(), Code::InvalidArgument); //todo!("Needs to be code AlreadyExists when mocked Error is corrected) -} - -#[tokio::test] -async fn get_project_user_has_access_returns_ok() { - let mut mock_contexts = get_mock_contexts(); - - let project = project::Model { - id: Default::default(), - name: "project".to_string(), - components_info: Default::default(), - owner_id: 0, - }; - - let access = access::Model { - id: Default::default(), - role: "Editor".to_string(), - project_id: 1, - user_id: 1, - }; - - let in_use = in_use::Model { - project_id: Default::default(), - session_id: 0, - latest_activity: Utc::now().naive_utc(), - }; - - let queries: Vec = vec![]; - - mock_contexts - .access_context_mock - .expect_get_access_by_uid_and_project_id() - .with(predicate::eq(0), predicate::eq(0)) - .returning(move |_, _| Ok(Some(access.clone()))); - - mock_contexts - .project_context_mock - .expect_get_by_id() - .with(predicate::eq(0)) - .returning(move |_| Ok(Some(project.clone()))); - - mock_contexts - .in_use_context_mock - .expect_get_by_id() - .with(predicate::eq(0)) - .returning(move |_| Ok(Some(in_use.clone()))); - - mock_contexts - .query_context_mock - .expect_get_all_by_project_id() - .with(predicate::eq(0)) - .returning(move |_| Ok(queries.clone())); - - let mut request = Request::new(GetProjectRequest { id: 0 }); - - request.metadata_mut().insert("uid", "0".parse().unwrap()); - - let contexts = disguise_context_mocks(mock_contexts); - let project_logic = ProjectController::new(contexts); - - let res = project_logic.get_project(request).await; - - assert!(res.is_ok()); -} - -#[tokio::test] -async fn delete_not_owner_returns_err() { - let mut mock_contexts = get_mock_contexts(); - - mock_contexts - .project_context_mock - .expect_get_by_id() - .with(predicate::eq(1)) - .returning(move |_| { - Ok(Some(project::Model { - id: 1, - name: Default::default(), - components_info: Default::default(), - owner_id: 2, - })) - }); - - let mut request = Request::new(DeleteProjectRequest { id: 1 }); - - request - .metadata_mut() - .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); - - let contexts = disguise_context_mocks(mock_contexts); - let project_logic = ProjectController::new(contexts); - - let res = project_logic.delete_project(request).await.unwrap_err(); - - assert_eq!(res.code(), Code::PermissionDenied); -} - -#[tokio::test] -async fn delete_invalid_project_returns_err() { - let mut mock_contexts = get_mock_contexts(); - - mock_contexts - .project_context_mock - .expect_get_by_id() - .with(predicate::eq(2)) - .returning(move |_| Ok(None)); - - let mut request = Request::new(DeleteProjectRequest { id: 2 }); - - request - .metadata_mut() - .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); - - let contexts = disguise_context_mocks(mock_contexts); - let project_logic = ProjectController::new(contexts); - - let res = project_logic.delete_project(request).await.unwrap_err(); - - assert_eq!(res.code(), Code::NotFound); -} - -#[tokio::test] -async fn delete_project_returns_ok() { - let mut mock_contexts = get_mock_contexts(); - - mock_contexts - .project_context_mock - .expect_get_by_id() - .with(predicate::eq(1)) - .returning(move |_| { - Ok(Some(project::Model { - id: 1, - name: Default::default(), - components_info: Default::default(), - owner_id: 1, - })) - }); - - mock_contexts - .project_context_mock - .expect_delete() - .with(predicate::eq(1)) - .returning(move |_| { - Ok(project::Model { - id: 1, - name: Default::default(), - components_info: Default::default(), - owner_id: 1, - }) - }); - - let mut request = Request::new(DeleteProjectRequest { id: 1 }); - - request - .metadata_mut() - .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); - - let contexts = disguise_context_mocks(mock_contexts); - let project_logic = ProjectController::new(contexts); - - let res = project_logic.delete_project(request).await; - - assert!(res.is_ok()); -} - -#[tokio::test] -async fn get_project_user_has_no_access_returns_err() { - let mut mock_contexts = get_mock_contexts(); - - let project = project::Model { - id: Default::default(), - name: "project".to_string(), - components_info: Default::default(), - owner_id: 0, - }; - - let in_use = in_use::Model { - project_id: Default::default(), - session_id: 0, - latest_activity: Default::default(), - }; - - let queries: Vec = vec![]; - - mock_contexts - .access_context_mock - .expect_get_access_by_uid_and_project_id() - .with(predicate::eq(0), predicate::eq(0)) - .returning(move |_, _| Ok(None)); - - mock_contexts - .project_context_mock - .expect_get_by_id() - .with(predicate::eq(0)) - .returning(move |_| Ok(Some(project.clone()))); - - mock_contexts - .in_use_context_mock - .expect_get_by_id() - .with(predicate::eq(0)) - .returning(move |_| Ok(Some(in_use.clone()))); - - mock_contexts - .query_context_mock - .expect_get_all_by_project_id() - .with(predicate::eq(0)) - .returning(move |_| Ok(queries.clone())); - - let mut request = Request::new(GetProjectRequest { id: 0 }); - - request.metadata_mut().insert("uid", "0".parse().unwrap()); - - let contexts = disguise_context_mocks(mock_contexts); - let project_logic = ProjectController::new(contexts); - - let res = project_logic.get_project(request).await.unwrap_err(); - - assert!(res.code() == Code::PermissionDenied); -} - -#[tokio::test] -async fn get_project_is_in_use_is_true() { - let mut mock_contexts = get_mock_contexts(); - - let project = project::Model { - id: Default::default(), - name: "project".to_string(), - components_info: Default::default(), - owner_id: 0, - }; - - let access = access::Model { - id: Default::default(), - role: "Editor".to_string(), - project_id: 1, - user_id: 1, - }; - - let in_use = in_use::Model { - project_id: Default::default(), - session_id: 0, - latest_activity: Utc::now().naive_utc(), - }; - - let queries: Vec = vec![]; - - mock_contexts - .access_context_mock - .expect_get_access_by_uid_and_project_id() - .with(predicate::eq(0), predicate::eq(0)) - .returning(move |_, _| Ok(Some(access.clone()))); - - mock_contexts - .project_context_mock - .expect_get_by_id() - .with(predicate::eq(0)) - .returning(move |_| Ok(Some(project.clone()))); - - mock_contexts - .in_use_context_mock - .expect_get_by_id() - .with(predicate::eq(0)) - .returning(move |_| Ok(Some(in_use.clone()))); - - mock_contexts - .query_context_mock - .expect_get_all_by_project_id() - .with(predicate::eq(0)) - .returning(move |_| Ok(queries.clone())); - - let mut request = Request::new(GetProjectRequest { id: 0 }); - - request.metadata_mut().insert("uid", "0".parse().unwrap()); - - let contexts = disguise_context_mocks(mock_contexts); - let project_logic = ProjectController::new(contexts); - - let res = project_logic.get_project(request).await; - - assert!(res.unwrap().get_ref().in_use); -} - -#[tokio::test] -async fn get_project_is_in_use_is_false() { - let mut mock_contexts = get_mock_contexts(); - - let project = project::Model { - id: Default::default(), - name: "project".to_string(), - components_info: Default::default(), - owner_id: 0, - }; - - let access = access::Model { - id: Default::default(), - role: "Editor".to_string(), - project_id: 1, - user_id: 1, - }; - - let in_use = in_use::Model { - project_id: 0, - session_id: 0, - latest_activity: Default::default(), - }; - - let updated_in_use = in_use::Model { - project_id: 0, - session_id: 1, - latest_activity: Default::default(), - }; - - let session = session::Model { - id: 0, - refresh_token: "refresh_token".to_owned(), - access_token: "access_token".to_owned(), - updated_at: Default::default(), - user_id: Default::default(), - }; - - let queries: Vec = vec![]; - - mock_contexts - .access_context_mock - .expect_get_access_by_uid_and_project_id() - .with(predicate::eq(0), predicate::eq(0)) - .returning(move |_, _| Ok(Some(access.clone()))); - - mock_contexts - .project_context_mock - .expect_get_by_id() - .with(predicate::eq(0)) - .returning(move |_| Ok(Some(project.clone()))); - - mock_contexts - .in_use_context_mock - .expect_get_by_id() - .with(predicate::eq(0)) - .returning(move |_| Ok(Some(in_use.clone()))); - - mock_contexts - .session_context_mock - .expect_get_by_token() - .with( - predicate::eq(TokenType::AccessToken), - predicate::eq("access_token".to_owned()), - ) - .returning(move |_, _| Ok(Some(session.clone()))); - - mock_contexts - .query_context_mock - .expect_get_all_by_project_id() - .with(predicate::eq(0)) - .returning(move |_| Ok(queries.clone())); - - mock_contexts - .in_use_context_mock - .expect_update() - .returning(move |_| Ok(updated_in_use.clone())); - - let mut request = Request::new(GetProjectRequest { id: 0 }); - - request - .metadata_mut() - .insert("authorization", "Bearer access_token".parse().unwrap()); - request.metadata_mut().insert("uid", "0".parse().unwrap()); - - let contexts = disguise_context_mocks(mock_contexts); - let project_logic = ProjectController::new(contexts); - - let res = project_logic.get_project(request).await; - - assert!(!res.unwrap().get_ref().in_use); -} - -#[tokio::test] -async fn get_project_project_has_no_queries_queries_are_empty() { - let mut mock_contexts = get_mock_contexts(); - - let project = project::Model { - id: Default::default(), - name: "project".to_string(), - components_info: Default::default(), - owner_id: 0, - }; - - let access = access::Model { - id: Default::default(), - role: "Editor".to_string(), - project_id: 1, - user_id: 1, - }; - - let in_use = in_use::Model { - project_id: Default::default(), - session_id: 0, - latest_activity: Utc::now().naive_utc(), - }; - - let queries: Vec = vec![]; - - mock_contexts - .access_context_mock - .expect_get_access_by_uid_and_project_id() - .with(predicate::eq(0), predicate::eq(0)) - .returning(move |_, _| Ok(Some(access.clone()))); - - mock_contexts - .project_context_mock - .expect_get_by_id() - .with(predicate::eq(0)) - .returning(move |_| Ok(Some(project.clone()))); - - mock_contexts - .in_use_context_mock - .expect_get_by_id() - .with(predicate::eq(0)) - .returning(move |_| Ok(Some(in_use.clone()))); - - mock_contexts - .query_context_mock - .expect_get_all_by_project_id() - .with(predicate::eq(0)) - .returning(move |_| Ok(queries.clone())); - - let mut request = Request::new(GetProjectRequest { id: 0 }); - - request.metadata_mut().insert("uid", "0".parse().unwrap()); - - let contexts = disguise_context_mocks(mock_contexts); - let project_logic = ProjectController::new(contexts); - - let res = project_logic.get_project(request).await; - - assert!(res.unwrap().get_ref().queries.is_empty()); -} - -#[tokio::test] -async fn get_project_query_has_no_result_query_is_empty() { - let mut mock_contexts = get_mock_contexts(); - - let project = project::Model { - id: Default::default(), - name: "project".to_string(), - components_info: Default::default(), - owner_id: 0, - }; - - let access = access::Model { - id: Default::default(), - role: "Editor".to_string(), - project_id: 1, - user_id: 1, - }; - - let in_use = in_use::Model { - project_id: Default::default(), - session_id: 0, - latest_activity: Utc::now().naive_utc(), - }; - - let query = query::Model { - id: 0, - project_id: 1, - string: "query".to_owned(), - result: None, - outdated: false, - }; - - let queries: Vec = vec![query]; - - mock_contexts - .access_context_mock - .expect_get_access_by_uid_and_project_id() - .with(predicate::eq(0), predicate::eq(0)) - .returning(move |_, _| Ok(Some(access.clone()))); - - mock_contexts - .project_context_mock - .expect_get_by_id() - .with(predicate::eq(0)) - .returning(move |_| Ok(Some(project.clone()))); - - mock_contexts - .in_use_context_mock - .expect_get_by_id() - .with(predicate::eq(0)) - .returning(move |_| Ok(Some(in_use.clone()))); - - mock_contexts - .query_context_mock - .expect_get_all_by_project_id() - .with(predicate::eq(0)) - .returning(move |_| Ok(queries.clone())); - - let mut request = Request::new(GetProjectRequest { id: 0 }); - - request.metadata_mut().insert("uid", "0".parse().unwrap()); - - let contexts = disguise_context_mocks(mock_contexts); - let project_logic = ProjectController::new(contexts); - - let res = project_logic.get_project(request).await; - - assert!(res.unwrap().get_ref().queries[0].result.is_empty()); -} - -#[tokio::test] -async fn list_projects_info_returns_ok() { - let mut mock_contexts = get_mock_contexts(); - - let project_info = ProjectInfo { - project_id: 1, - project_name: "project::Model name".to_owned(), - project_owner_id: 1, - user_role_on_project: "Editor".to_owned(), - }; - - mock_contexts - .project_context_mock - .expect_get_project_info_by_uid() - .with(predicate::eq(1)) - .returning(move |_| Ok(vec![project_info.clone()])); - - let mut list_projects_info_request = Request::new(()); - - list_projects_info_request - .metadata_mut() - .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); - - let contexts = disguise_context_mocks(mock_contexts); - let project_logic = ProjectController::new(contexts); - - let res = project_logic - .list_projects_info(list_projects_info_request) - .await; - - assert!(res.is_ok()); -} - -#[tokio::test] -async fn list_projects_info_returns_err() { - let mut mock_contexts = get_mock_contexts(); - - mock_contexts - .project_context_mock - .expect_get_project_info_by_uid() - .with(predicate::eq(1)) - .returning(move |_| Ok(vec![])); - - let mut list_projects_info_request = Request::new(()); - - list_projects_info_request - .metadata_mut() - .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); - - let contexts = disguise_context_mocks(mock_contexts); - let project_logic = ProjectController::new(contexts); - - let res = project_logic - .list_projects_info(list_projects_info_request) - .await; - - assert!(res.is_err()); -} - -#[tokio::test] -async fn update_name_returns_ok() { - let mut mock_contexts = get_mock_contexts(); - - let user_id = 1; - let project_id = 1; - let new_project_name = "new_name".to_string(); - - let mut update_project_request = Request::new(UpdateProjectRequest { - id: project_id, - name: Some(new_project_name.clone()), - components_info: None, - owner_id: None, - }); - - update_project_request.metadata_mut().insert( - "authorization", - metadata::MetadataValue::from_str("Bearer access_token").unwrap(), - ); - - update_project_request.metadata_mut().insert( - "uid", - metadata::MetadataValue::from_str(user_id.to_string().as_str()).unwrap(), - ); - - mock_contexts - .project_context_mock - .expect_get_by_id() - .with(predicate::eq(project_id)) - .returning(move |_| { - Ok(Some(project::Model { - id: project_id, - name: "old_name".to_owned(), - components_info: Default::default(), - owner_id: user_id, - })) - }); - - mock_contexts - .access_context_mock - .expect_get_access_by_uid_and_project_id() - .with(predicate::eq(1), predicate::eq(project_id)) - .returning(move |_, _| { - Ok(Some(access::Model { - id: 1, - user_id, - project_id, - role: "Editor".to_string(), - })) - }); - - mock_contexts - .session_context_mock - .expect_get_by_token() - .with( - predicate::eq(TokenType::AccessToken), - predicate::eq("access_token".to_string()), - ) - .returning(move |_, _| { - Ok(Some(session::Model { - id: 1, - refresh_token: "refresh_token".to_string(), - access_token: "access_token".to_string(), - updated_at: Default::default(), - user_id, - })) - }); - - mock_contexts - .project_context_mock - .expect_update() - .returning(move |_| { - Ok(project::Model { - id: project_id, - name: new_project_name.clone(), - components_info: Default::default(), - owner_id: user_id, - }) - }); - - mock_contexts - .in_use_context_mock - .expect_get_by_id() - .returning(move |_| { - Ok(Some(in_use::Model { - project_id, - session_id: 1, - latest_activity: Utc::now().naive_utc(), - })) - }); - - mock_contexts - .in_use_context_mock - .expect_update() - .returning(move |_| { - Ok(in_use::Model { - project_id: 1, - session_id: 1, - latest_activity: Utc::now().naive_utc(), - }) - }); - - let contexts = disguise_context_mocks(mock_contexts); - let project_logic = ProjectController::new(contexts); - - let res = project_logic.update_project(update_project_request).await; - - assert!(res.is_ok()); -} - -#[tokio::test] -async fn update_components_info_returns_ok() { - let mut mock_contexts = get_mock_contexts(); - - let user_id = 1; - let project_id = 1; - let components_info_non_json = ComponentsInfo { - components: vec![Component { - rep: Some(Rep::Json("a".to_owned())), - }], - components_hash: 1234456, - }; - let components_info = serde_json::to_value(components_info_non_json.clone()).unwrap(); - - let mut update_project_request = Request::new(UpdateProjectRequest { - id: project_id, - name: None, - components_info: Some(components_info_non_json.clone()), - owner_id: None, - }); - - update_project_request.metadata_mut().insert( - "authorization", - metadata::MetadataValue::from_str("Bearer access_token").unwrap(), - ); - - update_project_request.metadata_mut().insert( - "uid", - metadata::MetadataValue::from_str(user_id.to_string().as_str()).unwrap(), - ); - - mock_contexts - .project_context_mock - .expect_get_by_id() - .with(predicate::eq(project_id)) - .returning(move |_| { - Ok(Some(project::Model { - id: project_id, - name: Default::default(), - components_info: Default::default(), - owner_id: user_id, - })) - }); - - mock_contexts - .access_context_mock - .expect_get_access_by_uid_and_project_id() - .with(predicate::eq(1), predicate::eq(project_id)) - .returning(move |_, _| { - Ok(Some(access::Model { - id: 1, - user_id, - project_id, - role: "Editor".to_string(), - })) - }); - - mock_contexts - .session_context_mock - .expect_get_by_token() - .with( - predicate::eq(TokenType::AccessToken), - predicate::eq("access_token".to_string()), - ) - .returning(move |_, _| { - Ok(Some(session::Model { - id: 1, - refresh_token: "refresh_token".to_string(), - access_token: "access_token".to_string(), - updated_at: Default::default(), - user_id, - })) - }); - - mock_contexts - .project_context_mock - .expect_update() - .returning(move |_| { - Ok(project::Model { - id: project_id, - name: Default::default(), - components_info: components_info.clone(), - owner_id: user_id, - }) - }); - - mock_contexts - .in_use_context_mock - .expect_get_by_id() - .returning(move |_| { - Ok(Some(in_use::Model { - project_id, - session_id: 1, - latest_activity: Utc::now().naive_utc(), - })) - }); - - mock_contexts - .in_use_context_mock - .expect_update() - .returning(move |_| { - Ok(in_use::Model { - project_id: 1, - session_id: 1, - latest_activity: Utc::now().naive_utc(), - }) - }); - - let contexts = disguise_context_mocks(mock_contexts); - let project_logic = ProjectController::new(contexts); - - let res = project_logic.update_project(update_project_request).await; - - assert!(res.is_ok()); -} - -#[tokio::test] -async fn update_owner_id_returns_ok() { - let mut mock_contexts = get_mock_contexts(); - - let user_id = 1; - let project_id = 1; - let new_owner_id = 2; - - let mut update_project_request = Request::new(UpdateProjectRequest { - id: project_id, - name: None, - components_info: None, - owner_id: Some(new_owner_id), - }); - - update_project_request.metadata_mut().insert( - "authorization", - metadata::MetadataValue::from_str("Bearer access_token").unwrap(), - ); - - update_project_request.metadata_mut().insert( - "uid", - metadata::MetadataValue::from_str(user_id.to_string().as_str()).unwrap(), - ); - - mock_contexts - .project_context_mock - .expect_get_by_id() - .with(predicate::eq(project_id)) - .returning(move |_| { - Ok(Some(project::Model { - id: project_id, - name: Default::default(), - components_info: Default::default(), - owner_id: user_id, - })) - }); - - mock_contexts - .access_context_mock - .expect_get_access_by_uid_and_project_id() - .with(predicate::eq(1), predicate::eq(project_id)) - .returning(move |_, _| { - Ok(Some(access::Model { - id: 1, - user_id, - project_id, - role: "Editor".to_string(), - })) - }); - - mock_contexts - .session_context_mock - .expect_get_by_token() - .with( - predicate::eq(TokenType::AccessToken), - predicate::eq("access_token".to_string()), - ) - .returning(move |_, _| { - Ok(Some(session::Model { - id: 1, - refresh_token: "refresh_token".to_string(), - access_token: "access_token".to_string(), - updated_at: Default::default(), - user_id, - })) - }); - - mock_contexts - .project_context_mock - .expect_update() - .returning(move |_| { - Ok(project::Model { - id: project_id, - name: Default::default(), - components_info: Default::default(), - owner_id: new_owner_id, - }) - }); - - mock_contexts - .in_use_context_mock - .expect_get_by_id() - .returning(move |_| { - Ok(Some(in_use::Model { - project_id, - session_id: 1, - latest_activity: Utc::now().naive_utc(), - })) - }); - - mock_contexts - .in_use_context_mock - .expect_update() - .returning(move |_| { - Ok(in_use::Model { - project_id: 1, - session_id: 1, - latest_activity: Utc::now().naive_utc(), - }) - }); - - let contexts = disguise_context_mocks(mock_contexts); - let project_logic = ProjectController::new(contexts); - - let res = project_logic.update_project(update_project_request).await; - - assert!(res.is_ok()); -} - -#[tokio::test] -async fn update_returns_ok() { - let mut mock_contexts = get_mock_contexts(); - - let user_id = 1; - let project_id = 1; - let new_project_name = "new_name".to_string(); - let new_components_info_non_json = ComponentsInfo { - components: vec![Component { - rep: Some(Rep::Json("a".to_owned())), - }], - components_hash: 1234456, - }; - let new_components_info = serde_json::to_value(new_components_info_non_json.clone()).unwrap(); - let new_owner_id = 2; - - let mut update_project_request = Request::new(UpdateProjectRequest { - id: project_id, - name: Some(new_project_name.clone()), - components_info: Some(new_components_info_non_json.clone()), - owner_id: Some(new_owner_id), - }); - - update_project_request.metadata_mut().insert( - "authorization", - metadata::MetadataValue::from_str("Bearer access_token").unwrap(), - ); - - update_project_request.metadata_mut().insert( - "uid", - metadata::MetadataValue::from_str(user_id.to_string().as_str()).unwrap(), - ); - - mock_contexts - .project_context_mock - .expect_get_by_id() - .with(predicate::eq(project_id)) - .returning(move |_| { - Ok(Some(project::Model { - id: project_id, - name: "old_name".to_owned(), - components_info: serde_json::to_value("{\"old_components\":1}").unwrap(), - owner_id: user_id, - })) - }); - - mock_contexts - .access_context_mock - .expect_get_access_by_uid_and_project_id() - .with(predicate::eq(1), predicate::eq(project_id)) - .returning(move |_, _| { - Ok(Some(access::Model { - id: 1, - user_id, - project_id, - role: "Editor".to_string(), - })) - }); - - mock_contexts - .session_context_mock - .expect_get_by_token() - .with( - predicate::eq(TokenType::AccessToken), - predicate::eq("access_token".to_string()), - ) - .returning(move |_, _| { - Ok(Some(session::Model { - id: 1, - refresh_token: "refresh_token".to_string(), - access_token: "access_token".to_string(), - updated_at: Default::default(), - user_id, - })) - }); - - mock_contexts - .project_context_mock - .expect_update() - .returning(move |_| { - Ok(project::Model { - id: project_id, - name: new_project_name.clone(), - components_info: new_components_info.clone(), - owner_id: new_owner_id, - }) - }); - - mock_contexts - .in_use_context_mock - .expect_get_by_id() - .returning(move |_| { - Ok(Some(in_use::Model { - project_id, - session_id: 1, - latest_activity: Utc::now().naive_utc(), - })) - }); - - mock_contexts - .in_use_context_mock - .expect_update() - .returning(move |_| { - Ok(in_use::Model { - project_id: 1, - session_id: 1, - latest_activity: Utc::now().naive_utc(), - }) - }); - - let contexts = disguise_context_mocks(mock_contexts); - let project_logic = ProjectController::new(contexts); - - let res = project_logic.update_project(update_project_request).await; - - assert!(res.is_ok()); -} - -#[tokio::test] -async fn update_owner_not_owner_returns_err() { - let mut mock_contexts = get_mock_contexts(); - - mock_contexts - .project_context_mock - .expect_get_by_id() - .with(predicate::eq(1)) - .returning(move |_| { - Ok(Some(project::Model { - id: 1, - name: Default::default(), - components_info: Default::default(), - owner_id: 2, - })) - }); - - mock_contexts - .access_context_mock - .expect_get_access_by_uid_and_project_id() - .with(predicate::eq(1), predicate::eq(1)) - .returning(move |_, _| { - Ok(Some(access::Model { - id: 1, - user_id: 1, - project_id: 1, - role: "Editor".to_owned(), - })) - }); - - mock_contexts - .session_context_mock - .expect_get_by_token() - .with( - predicate::eq(TokenType::AccessToken), - predicate::eq("access_token".to_string()), - ) - .returning(move |_, _| { - Ok(Some(session::Model { - id: 1, - refresh_token: "refresh_token".to_string(), - access_token: "access_token".to_string(), - updated_at: Default::default(), - user_id: 1, - })) - }); - - mock_contexts - .in_use_context_mock - .expect_get_by_id() - .with(predicate::eq(1)) - .returning(move |_| { - Ok(Some(in_use::Model { - session_id: 1, - latest_activity: Default::default(), - project_id: 1, - })) - }); - - mock_contexts - .in_use_context_mock - .expect_update() - .returning(move |_| { - Ok(in_use::Model { - session_id: 1, - latest_activity: Default::default(), - project_id: 1, - }) - }); - - let mut request = Request::new(UpdateProjectRequest { - id: 1, - name: None, - components_info: None, - owner_id: Some(1), - }); - - request - .metadata_mut() - .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); - - request.metadata_mut().insert( - "authorization", - metadata::MetadataValue::from_str("access_token").unwrap(), - ); - - let contexts = disguise_context_mocks(mock_contexts); - let project_logic = ProjectController::new(contexts); - - let res = project_logic.update_project(request).await.unwrap_err(); - - assert_eq!(res.code(), Code::PermissionDenied); -} - -#[tokio::test] -async fn update_no_in_use_returns_err() { - let mut mock_contexts = get_mock_contexts(); - - mock_contexts - .project_context_mock - .expect_get_by_id() - .with(predicate::eq(1)) - .returning(move |_| { - Ok(Some(project::Model { - id: 1, - name: Default::default(), - components_info: Default::default(), - owner_id: 1, - })) - }); - - mock_contexts - .access_context_mock - .expect_get_access_by_uid_and_project_id() - .with(predicate::eq(1), predicate::eq(1)) - .returning(move |_, _| { - Ok(Some(access::Model { - id: 1, - user_id: 1, - project_id: 1, - role: "Editor".to_owned(), - })) - }); - - mock_contexts - .session_context_mock - .expect_get_by_token() - .with( - predicate::eq(TokenType::AccessToken), - predicate::eq("access_token".to_string()), - ) - .returning(move |_, _| { - Ok(Some(session::Model { - id: 1, - refresh_token: "refresh_token".to_string(), - access_token: "access_token".to_string(), - updated_at: Default::default(), - user_id: 1, - })) - }); - - mock_contexts - .in_use_context_mock - .expect_get_by_id() - .with(predicate::eq(1)) - .returning(move |_| { - Ok(Some(in_use::Model { - session_id: 2, - latest_activity: Utc::now().naive_utc(), - project_id: 1, - })) - }); - - let mut request = Request::new(UpdateProjectRequest { - id: 1, - name: None, - components_info: None, - owner_id: None, - }); - - request - .metadata_mut() - .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); - - request.metadata_mut().insert( - "authorization", - metadata::MetadataValue::from_str("access_token").unwrap(), - ); - - let contexts = disguise_context_mocks(mock_contexts); - let project_logic = ProjectController::new(contexts); - - let res = project_logic.update_project(request).await.unwrap_err(); - - assert_eq!(res.code(), Code::FailedPrecondition); -} - -#[tokio::test] -async fn update_no_access_returns_err() { - let mut mock_contexts = get_mock_contexts(); - - mock_contexts - .project_context_mock - .expect_get_by_id() - .with(predicate::eq(1)) - .returning(move |_| { - Ok(Some(project::Model { - id: 1, - name: Default::default(), - components_info: Default::default(), - owner_id: 1, - })) - }); - - mock_contexts - .access_context_mock - .expect_get_access_by_uid_and_project_id() - .with(predicate::eq(1), predicate::eq(1)) - .returning(move |_, _| Ok(None)); - - let mut request = Request::new(UpdateProjectRequest { - id: 1, - name: None, - components_info: None, - owner_id: None, - }); - - request - .metadata_mut() - .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); - - let contexts = disguise_context_mocks(mock_contexts); - let project_logic = ProjectController::new(contexts); - - let res = project_logic.update_project(request).await.unwrap_err(); - - assert_eq!(res.code(), Code::PermissionDenied); -} - -#[tokio::test] -async fn update_incorrect_role_returns_err() { - let mut mock_contexts = get_mock_contexts(); - - mock_contexts - .project_context_mock - .expect_get_by_id() - .with(predicate::eq(1)) - .returning(move |_| { - Ok(Some(project::Model { - id: 1, - name: Default::default(), - components_info: Default::default(), - owner_id: 1, - })) - }); - - mock_contexts - .access_context_mock - .expect_get_access_by_uid_and_project_id() - .with(predicate::eq(1), predicate::eq(1)) - .returning(move |_, _| { - Ok(Some(access::Model { - id: 1, - user_id: 1, - project_id: 1, - role: "Viewer".to_owned(), - })) - }); - - let mut request = Request::new(UpdateProjectRequest { - id: 1, - name: None, - components_info: None, - owner_id: None, - }); - - request - .metadata_mut() - .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); - - let contexts = disguise_context_mocks(mock_contexts); - let project_logic = ProjectController::new(contexts); - - let res = project_logic.update_project(request).await.unwrap_err(); - - assert_eq!(res.code(), Code::PermissionDenied); -} - -#[tokio::test] -async fn update_no_session_returns_err() { - let mut mock_contexts = get_mock_contexts(); - - mock_contexts - .project_context_mock - .expect_get_by_id() - .with(predicate::eq(1)) - .returning(move |_| { - Ok(Some(project::Model { - id: 1, - name: Default::default(), - components_info: Default::default(), - owner_id: 1, - })) - }); - - mock_contexts - .access_context_mock - .expect_get_access_by_uid_and_project_id() - .with(predicate::eq(1), predicate::eq(1)) - .returning(move |_, _| { - Ok(Some(access::Model { - id: 1, - user_id: 1, - project_id: 1, - role: "Editor".to_owned(), - })) - }); - - mock_contexts - .session_context_mock - .expect_get_by_token() - .with( - predicate::eq(TokenType::AccessToken), - predicate::eq("access_token".to_string()), - ) - .returning(move |_, _| Ok(None)); - - let mut request = Request::new(UpdateProjectRequest { - id: 1, - name: None, - components_info: None, - owner_id: None, - }); - - request - .metadata_mut() - .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); - - request.metadata_mut().insert( - "authorization", - metadata::MetadataValue::from_str("access_token").unwrap(), - ); - - let contexts = disguise_context_mocks(mock_contexts); - let project_logic = ProjectController::new(contexts); - - let res = project_logic.update_project(request).await.unwrap_err(); - - assert_eq!(res.code(), Code::Unauthenticated); -} - -#[tokio::test] -async fn update_no_project_returns_err() { - let mut mock_contexts = get_mock_contexts(); - - mock_contexts - .project_context_mock - .expect_get_by_id() - .with(predicate::eq(2)) - .returning(move |_| Ok(None)); - - let mut request = Request::new(UpdateProjectRequest { - id: 2, - name: None, - components_info: None, - owner_id: None, - }); - - request - .metadata_mut() - .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); - - let contexts = disguise_context_mocks(mock_contexts); - let project_logic = ProjectController::new(contexts); - - let res = project_logic.update_project(request).await.unwrap_err(); - - assert_eq!(res.code(), Code::NotFound); -} diff --git a/src/tests/controllers/query_controller.rs b/src/tests/controllers/query_controller.rs deleted file mode 100644 index f836bf6..0000000 --- a/src/tests/controllers/query_controller.rs +++ /dev/null @@ -1,595 +0,0 @@ -use crate::api::server::protobuf::query_response::{self, Result}; -use crate::api::server::protobuf::{ - CreateQueryRequest, DeleteQueryRequest, QueryResponse, SendQueryRequest, UpdateQueryRequest, -}; -use crate::controllers::controller_impls::QueryController; -use crate::controllers::controller_traits::QueryControllerTrait; -use crate::entities::{access, project, query}; -use crate::tests::controllers::helpers::{ - disguise_context_mocks, disguise_service_mocks, get_mock_contexts, get_mock_services, -}; -use mockall::predicate; -use sea_orm::DbErr; -use std::str::FromStr; -use tonic::{metadata, Code, Request, Response}; - -#[tokio::test] -async fn create_invalid_query_returns_err() { - let mut mock_contexts = get_mock_contexts(); - let mock_services = get_mock_services(); - - let query = query::Model { - id: Default::default(), - string: "".to_string(), - result: Default::default(), - project_id: 1, - outdated: Default::default(), - }; - - let access = access::Model { - id: Default::default(), - role: "Editor".to_string(), - project_id: 1, - user_id: 1, - }; - - mock_contexts - .access_context_mock - .expect_get_access_by_uid_and_project_id() - .with(predicate::eq(1), predicate::eq(1)) - .returning(move |_, _| Ok(Some(access.clone()))); - - mock_contexts - .query_context_mock - .expect_create() - .with(predicate::eq(query.clone())) - .returning(move |_| Err(DbErr::RecordNotInserted)); - - let mut request = Request::new(CreateQueryRequest { - string: "".to_string(), - project_id: 1, - }); - - request - .metadata_mut() - .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); - - let contexts = disguise_context_mocks(mock_contexts); - let services = disguise_service_mocks(mock_services); - let query_logic = QueryController::new(contexts, services); - - let res = query_logic.create_query(request).await.unwrap_err(); - - assert_eq!(res.code(), Code::Internal); -} - -#[tokio::test] -async fn create_query_returns_ok() { - let mut mock_contexts = get_mock_contexts(); - let mock_services = get_mock_services(); - - let query = query::Model { - id: Default::default(), - string: "".to_string(), - result: Default::default(), - project_id: 1, - outdated: Default::default(), - }; - - let access = access::Model { - id: Default::default(), - role: "Editor".to_string(), - project_id: 1, - user_id: 1, - }; - - mock_contexts - .access_context_mock - .expect_get_access_by_uid_and_project_id() - .with(predicate::eq(1), predicate::eq(1)) - .returning(move |_, _| Ok(Some(access.clone()))); - - mock_contexts - .query_context_mock - .expect_create() - .with(predicate::eq(query.clone())) - .returning(move |_| Ok(query.clone())); - - let mut request = Request::new(CreateQueryRequest { - string: "".to_string(), - project_id: 1, - }); - - request - .metadata_mut() - .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); - - let contexts = disguise_context_mocks(mock_contexts); - let services = disguise_service_mocks(mock_services); - let query_logic = QueryController::new(contexts, services); - - let res = query_logic.create_query(request).await; - - assert!(res.is_ok()); -} - -#[tokio::test] -async fn update_invalid_query_returns_err() { - let mut mock_contexts = get_mock_contexts(); - let mock_services = get_mock_services(); - - let old_query = query::Model { - id: 1, - string: "".to_string(), - result: None, - project_id: Default::default(), - outdated: true, - }; - - let query = query::Model { - string: "updated".to_string(), - ..old_query.clone() - }; - - let access = access::Model { - id: 1, - role: "Editor".to_string(), - project_id: Default::default(), - user_id: 1, - }; - - mock_contexts - .query_context_mock - .expect_get_by_id() - .with(predicate::eq(1)) - .returning(move |_| Ok(Some(old_query.clone()))); - - mock_contexts - .access_context_mock - .expect_get_access_by_uid_and_project_id() - .with(predicate::eq(1), predicate::eq(0)) - .returning(move |_, _| Ok(Some(access.clone()))); - - mock_contexts - .query_context_mock - .expect_update() - .with(predicate::eq(query.clone())) - .returning(move |_| Err(DbErr::RecordNotUpdated)); - - let mut request = Request::new(UpdateQueryRequest { - id: 1, - string: "updated".to_string(), - }); - - request - .metadata_mut() - .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); - - let contexts = disguise_context_mocks(mock_contexts); - let services = disguise_service_mocks(mock_services); - let query_logic = QueryController::new(contexts, services); - - let res = query_logic.update_query(request).await.unwrap_err(); - - assert_eq!(res.code(), Code::Internal); -} - -#[tokio::test] -async fn update_query_returns_ok() { - let mut mock_contexts = get_mock_contexts(); - let mock_services = get_mock_services(); - - let old_query = query::Model { - id: 1, - string: "".to_string(), - result: None, - project_id: Default::default(), - outdated: true, - }; - - let query = query::Model { - string: "updated".to_string(), - ..old_query.clone() - }; - - let access = access::Model { - id: Default::default(), - role: "Editor".to_string(), - project_id: Default::default(), - user_id: 1, - }; - - mock_contexts - .access_context_mock - .expect_get_access_by_uid_and_project_id() - .with(predicate::eq(1), predicate::eq(0)) - .returning(move |_, _| Ok(Some(access.clone()))); - - mock_contexts - .query_context_mock - .expect_get_by_id() - .with(predicate::eq(1)) - .returning(move |_| Ok(Some(old_query.clone()))); - - mock_contexts - .query_context_mock - .expect_update() - .with(predicate::eq(query.clone())) - .returning(move |_| Ok(query.clone())); - - let mut request = Request::new(UpdateQueryRequest { - id: 1, - string: "updated".to_string(), - }); - - request - .metadata_mut() - .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); - - let contexts = disguise_context_mocks(mock_contexts); - let services = disguise_service_mocks(mock_services); - let query_logic = QueryController::new(contexts, services); - - let res = query_logic.update_query(request).await; - - assert!(res.is_ok()); -} - -#[tokio::test] -async fn delete_invalid_query_returns_err() { - let mut mock_contexts = get_mock_contexts(); - let mock_services = get_mock_services(); - - let access = access::Model { - id: Default::default(), - role: "Editor".to_string(), - project_id: Default::default(), - user_id: 1, - }; - - let query = query::Model { - id: 1, - string: "".to_string(), - result: Default::default(), - project_id: Default::default(), - outdated: Default::default(), - }; - - mock_contexts - .access_context_mock - .expect_get_access_by_uid_and_project_id() - .with(predicate::eq(1), predicate::eq(0)) - .returning(move |_, _| Ok(Some(access.clone()))); - - mock_contexts - .query_context_mock - .expect_get_by_id() - .with(predicate::eq(1)) - .returning(move |_| Ok(Some(query.clone()))); - - mock_contexts - .query_context_mock - .expect_delete() - .with(predicate::eq(1)) - .returning(move |_| Err(DbErr::RecordNotFound("".to_string()))); - - let mut request = Request::new(DeleteQueryRequest { id: 1 }); - - request - .metadata_mut() - .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); - - let contexts = disguise_context_mocks(mock_contexts); - let services = disguise_service_mocks(mock_services); - let query_logic = QueryController::new(contexts, services); - - let res = query_logic.delete_query(request).await.unwrap_err(); - - assert_eq!(res.code(), Code::NotFound); -} - -#[tokio::test] -async fn delete_query_returns_ok() { - let mut mock_contexts = get_mock_contexts(); - let mock_services = get_mock_services(); - - let query = query::Model { - id: 1, - string: "".to_string(), - result: Default::default(), - project_id: Default::default(), - outdated: Default::default(), - }; - - let query_clone = query.clone(); - - let access = access::Model { - id: Default::default(), - role: "Editor".to_string(), - project_id: Default::default(), - user_id: 1, - }; - - mock_contexts - .query_context_mock - .expect_get_by_id() - .with(predicate::eq(1)) - .returning(move |_| Ok(Some(query.clone()))); - - mock_contexts - .access_context_mock - .expect_get_access_by_uid_and_project_id() - .with(predicate::eq(1), predicate::eq(0)) - .returning(move |_, _| Ok(Some(access.clone()))); - - mock_contexts - .query_context_mock - .expect_delete() - .with(predicate::eq(1)) - .returning(move |_| Ok(query_clone.clone())); - - let mut request = Request::new(DeleteQueryRequest { id: 1 }); - - request - .metadata_mut() - .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); - - let contexts = disguise_context_mocks(mock_contexts); - let services = disguise_service_mocks(mock_services); - let query_logic = QueryController::new(contexts, services); - - let res = query_logic.delete_query(request).await; - - assert!(res.is_ok()); -} - -#[tokio::test] -async fn create_query_invalid_role_returns_err() { - let mut mock_contexts = get_mock_contexts(); - let mock_services = get_mock_services(); - - let query = query::Model { - id: 1, - string: "".to_string(), - result: Default::default(), - project_id: Default::default(), - outdated: Default::default(), - }; - - let access = access::Model { - id: Default::default(), - role: "Viewer".to_string(), - project_id: Default::default(), - user_id: 1, - }; - - mock_contexts - .access_context_mock - .expect_get_access_by_uid_and_project_id() - .with(predicate::eq(1), predicate::eq(1)) - .returning(move |_, _| Ok(Some(access.clone()))); - - mock_contexts - .query_context_mock - .expect_create() - .with(predicate::eq(query.clone())) - .returning(move |_| Ok(query.clone())); - - let mut request = Request::new(CreateQueryRequest { - string: "".to_string(), - project_id: 1, - }); - - request - .metadata_mut() - .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); - - let contexts = disguise_context_mocks(mock_contexts); - let services = disguise_service_mocks(mock_services); - let query_logic = QueryController::new(contexts, services); - - let res = query_logic.create_query(request).await.unwrap_err(); - - assert_eq!(res.code(), Code::PermissionDenied); -} - -#[tokio::test] -async fn delete_query_invalid_role_returns_err() { - let mut mock_contexts = get_mock_contexts(); - let mock_services = get_mock_services(); - - let query = query::Model { - id: 1, - string: "".to_string(), - result: Default::default(), - project_id: Default::default(), - outdated: Default::default(), - }; - - let query_clone = query.clone(); - - let access = access::Model { - id: Default::default(), - role: "Viewer".to_string(), - project_id: Default::default(), - user_id: 1, - }; - - mock_contexts - .query_context_mock - .expect_get_by_id() - .with(predicate::eq(1)) - .returning(move |_| Ok(Some(query.clone()))); - - mock_contexts - .access_context_mock - .expect_get_access_by_uid_and_project_id() - .with(predicate::eq(1), predicate::eq(0)) - .returning(move |_, _| Ok(Some(access.clone()))); - - mock_contexts - .query_context_mock - .expect_delete() - .with(predicate::eq(1)) - .returning(move |_| Ok(query_clone.clone())); - - let mut request = Request::new(DeleteQueryRequest { id: 1 }); - - request - .metadata_mut() - .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); - - let contexts = disguise_context_mocks(mock_contexts); - let services = disguise_service_mocks(mock_services); - let query_logic = QueryController::new(contexts, services); - - let res = query_logic.delete_query(request).await.unwrap_err(); - - assert_eq!(res.code(), Code::PermissionDenied); -} - -#[tokio::test] -async fn update_query_invalid_role_returns_err() { - let mut mock_contexts = get_mock_contexts(); - let mock_services = get_mock_services(); - - let old_query = query::Model { - id: 1, - string: "".to_string(), - result: None, - project_id: Default::default(), - outdated: true, - }; - - let query = query::Model { - string: "updated".to_string(), - ..old_query.clone() - }; - - let access = access::Model { - id: Default::default(), - role: "Viewer".to_string(), - project_id: Default::default(), - user_id: 1, - }; - - mock_contexts - .access_context_mock - .expect_get_access_by_uid_and_project_id() - .with(predicate::eq(1), predicate::eq(0)) - .returning(move |_, _| Ok(Some(access.clone()))); - - mock_contexts - .query_context_mock - .expect_get_by_id() - .with(predicate::eq(1)) - .returning(move |_| Ok(Some(old_query.clone()))); - - mock_contexts - .query_context_mock - .expect_update() - .with(predicate::eq(query.clone())) - .returning(move |_| Ok(query.clone())); - - let mut request = Request::new(UpdateQueryRequest { - id: 1, - string: "updated".to_string(), - }); - - request - .metadata_mut() - .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); - - let contexts = disguise_context_mocks(mock_contexts); - let services = disguise_service_mocks(mock_services); - let query_logic = QueryController::new(contexts, services); - - let res = query_logic.update_query(request).await.unwrap_err(); - - assert_eq!(res.code(), Code::PermissionDenied); -} - -#[tokio::test] -async fn send_query_returns_ok() { - let mut mock_contexts = get_mock_contexts(); - let mut mock_services = get_mock_services(); - - let query = query::Model { - id: Default::default(), - string: "".to_string(), - result: Default::default(), - project_id: Default::default(), - outdated: Default::default(), - }; - - let access = access::Model { - id: Default::default(), - role: "Editor".to_string(), - project_id: Default::default(), - user_id: 1, - }; - - let project = project::Model { - id: Default::default(), - name: "project".to_string(), - components_info: Default::default(), - owner_id: 0, - }; - - let query_response = QueryResponse { - query_id: Default::default(), - info: Default::default(), - result: Some(Result::Success(query_response::Success {})), - }; - - let updated_query = query::Model { - result: Some(serde_json::to_value(query_response.clone().result).unwrap()), - ..query.clone() - }; - - mock_contexts - .project_context_mock - .expect_get_by_id() - .with(predicate::eq(0)) - .returning(move |_| Ok(Some(project.clone()))); - - mock_contexts - .access_context_mock - .expect_get_access_by_uid_and_project_id() - .with(predicate::eq(1), predicate::eq(0)) - .returning(move |_, _| Ok(Some(access.clone()))); - - mock_contexts - .query_context_mock - .expect_get_by_id() - .with(predicate::eq(0)) - .returning(move |_| Ok(Some(query.clone()))); - - mock_services - .reveaal_service_mock - .expect_send_query() - .returning(move |_| Ok(Response::new(query_response.clone()))); - - mock_contexts - .query_context_mock - .expect_update() - .with(predicate::eq(updated_query.clone())) - .returning(move |_| Ok(updated_query.clone())); - - let mut request = Request::new(SendQueryRequest { - id: Default::default(), - project_id: Default::default(), - }); - - request - .metadata_mut() - .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); - - let contexts = disguise_context_mocks(mock_contexts); - let services = disguise_service_mocks(mock_services); - let query_logic = QueryController::new(contexts, services); - - let res = query_logic.send_query(request).await; - - assert!(res.is_ok()); -} diff --git a/src/tests/controllers/session_controller.rs b/src/tests/controllers/session_controller.rs deleted file mode 100644 index 99251de..0000000 --- a/src/tests/controllers/session_controller.rs +++ /dev/null @@ -1,337 +0,0 @@ -use mockall::predicate; -use std::env; -use std::str::FromStr; - -use crate::entities::{session, user}; -use crate::tests::controllers::helpers::{ - disguise_context_mocks, disguise_service_mocks, get_mock_contexts, get_mock_services, -}; - -use crate::api::auth::{Token, TokenType}; -use crate::api::server::protobuf::get_auth_token_request::{user_credentials, UserCredentials}; -use crate::api::server::protobuf::GetAuthTokenRequest; -use crate::controllers::controller_impls::SessionController; -use crate::controllers::controller_traits::SessionControllerTrait; -use sea_orm::DbErr; -use tonic::{metadata, Code, Request}; - -#[tokio::test] -async fn update_session_no_session_exists_creates_session_returns_err() { - let mut mock_contexts = get_mock_contexts(); - let mock_services = get_mock_services(); - - mock_contexts - .session_context_mock - .expect_get_by_token() - .returning(move |_, _| Ok(None)); - - mock_contexts - .session_context_mock - .expect_update() - .returning(move |_| Err(DbErr::RecordNotInserted)); - - let contexts = disguise_context_mocks(mock_contexts); - let services = disguise_service_mocks(mock_services); - let session_logic = SessionController::new(contexts, services); - - let res = session_logic - .update_session("old_refresh_token".to_string()) - .await; - - assert_eq!(res.unwrap_err().code(), Code::Unauthenticated); -} - -#[tokio::test] -async fn update_session_returns_new_tokens_when_session_exists() { - let mut mock_contexts = get_mock_contexts(); - let mock_services = get_mock_services(); - - let refresh_token = "refresh_token".to_string(); - - mock_contexts - .session_context_mock - .expect_get_by_token() - .times(1) - .returning(|_, _| { - Ok(Some(session::Model { - id: 0, - access_token: "old_access_token".to_string(), - refresh_token: "old_refresh_token".to_string(), - updated_at: Default::default(), - user_id: 1, - })) - }); - - mock_contexts - .session_context_mock - .expect_update() - .times(1) - .returning(move |_| { - Ok(session::Model { - id: 0, - refresh_token: "refresh_token".to_string(), - access_token: "access_token".to_string(), - updated_at: Default::default(), - user_id: 1, - }) - }); - - let contexts = disguise_context_mocks(mock_contexts); - let services = disguise_service_mocks(mock_services); - let session_logic = SessionController::new(contexts, services); - - let result = session_logic.update_session(refresh_token).await; - - assert!(result.is_ok()); - let (access_token, refresh_token) = result.unwrap(); - assert_ne!(access_token.to_string(), "old_access_token"); - assert_ne!(refresh_token.to_string(), "old_refresh_token"); -} - -#[tokio::test] -async fn update_session_returns_error_when_no_session_found() { - let mut mock_contexts = get_mock_contexts(); - let mock_services = get_mock_services(); - - let refresh_token = "refresh_token".to_string(); - - mock_contexts - .session_context_mock - .expect_get_by_token() - .times(1) - .returning(|_, _| Ok(None)); - - let contexts = disguise_context_mocks(mock_contexts); - let services = disguise_service_mocks(mock_services); - let session_logic = SessionController::new(contexts, services); - - let result = session_logic.update_session(refresh_token).await; - - assert!(result.is_err()); - assert_eq!(result.unwrap_err().code(), Code::Unauthenticated); -} - -#[tokio::test] -async fn update_session_returns_error_when_database_error_occurs() { - let mut mock_contexts = get_mock_contexts(); - let mock_services = get_mock_services(); - - let refresh_token = "refresh_token".to_string(); - - mock_contexts - .session_context_mock - .expect_get_by_token() - .times(1) - .returning(|_, _| Err(DbErr::RecordNotFound("".to_string()))); - - let contexts = disguise_context_mocks(mock_contexts); - let services = disguise_service_mocks(mock_services); - let session_logic = SessionController::new(contexts, services); - - let result = session_logic.update_session(refresh_token).await; - - assert!(result.is_err()); - assert_eq!(result.unwrap_err().code(), Code::Internal); -} - -#[tokio::test] -async fn get_auth_token_from_credentials_returns_ok() { - let mut mock_contexts = get_mock_contexts(); - let mut mock_services = get_mock_services(); - - let request = GetAuthTokenRequest { - user_credentials: Option::from(UserCredentials { - password: "Password123".to_string(), - user: Option::from(user_credentials::User::Username("Example".to_string())), - }), - }; - - mock_contexts - .user_context_mock - .expect_get_by_username() - .returning(move |_| { - Ok(Option::from(user::Model { - id: 1, - email: "".to_string(), - username: "Example".to_string(), - password: "".to_string(), - })) - }); - - mock_services - .hashing_service_mock - .expect_verify_password() - .returning(move |_, _| Ok(true)); - - mock_contexts - .session_context_mock - .expect_create() - .returning(move |_| { - Ok(session::Model { - id: 0, - refresh_token: "refresh_token".to_string(), - access_token: "access_token".to_string(), - updated_at: Default::default(), - user_id: 1, - }) - }); - - let contexts = disguise_context_mocks(mock_contexts); - let services = disguise_service_mocks(mock_services); - let session_logic = SessionController::new(contexts, services); - - let response = session_logic - .get_auth_token(Request::new(request)) - .await - .unwrap(); - - assert!(!response.get_ref().refresh_token.is_empty()); - assert!(!response.get_ref().access_token.is_empty()); -} - -#[tokio::test] -async fn get_auth_token_from_token_returns_ok() { - env::set_var("REFRESH_TOKEN_HS512_SECRET", "refresh_secret"); - - let mut mock_contexts = get_mock_contexts(); - let mock_services = get_mock_services(); - - let mut request = Request::new(GetAuthTokenRequest { - user_credentials: None, - }); - - let refresh_token = Token::new(TokenType::RefreshToken, "1").unwrap(); - - request.metadata_mut().insert( - "authorization", - metadata::MetadataValue::from_str(format!("Bearer {}", refresh_token).as_str()).unwrap(), - ); - - mock_contexts - .session_context_mock - .expect_get_by_token() - .returning(move |_, _| { - Ok(Option::from(session::Model { - id: 0, - refresh_token: "refresh_token".to_string(), - access_token: "access_token".to_string(), - updated_at: Default::default(), - user_id: 1, - })) - }); - - mock_contexts - .session_context_mock - .expect_update() - .returning(move |_| { - Ok(session::Model { - id: 0, - refresh_token: "refresh_token".to_string(), - access_token: "access_token".to_string(), - updated_at: Default::default(), - user_id: 1, - }) - }); - - let contexts = disguise_context_mocks(mock_contexts); - let services = disguise_service_mocks(mock_services); - let session_logic = SessionController::new(contexts, services); - - let response = session_logic.get_auth_token(request).await.unwrap(); - - assert!(!response.get_ref().refresh_token.is_empty()); - assert!(!response.get_ref().access_token.is_empty()); -} - -#[tokio::test] -async fn get_auth_token_from_invalid_token_returns_err() { - let mock_contexts = get_mock_contexts(); - let mock_services = get_mock_services(); - - let mut request = Request::new(GetAuthTokenRequest { - user_credentials: None, - }); - - request.metadata_mut().insert( - "authorization", - metadata::MetadataValue::from_str("invalid token").unwrap(), - ); - - let contexts = disguise_context_mocks(mock_contexts); - let services = disguise_service_mocks(mock_services); - let session_logic = SessionController::new(contexts, services); - - let response = session_logic.get_auth_token(request).await; - - assert_eq!(response.unwrap_err().code(), Code::Unauthenticated); -} - -#[tokio::test] -async fn delete_session_returns_ok() { - let mut mock_contexts = get_mock_contexts(); - let mock_services = get_mock_services(); - - mock_contexts - .session_context_mock - .expect_delete_by_token() - .with( - predicate::eq(TokenType::AccessToken), - predicate::eq("test_token".to_string()), - ) - .returning(move |_, _| { - Ok(session::Model { - id: 1, - refresh_token: Default::default(), - access_token: "test_token".to_string(), - updated_at: Default::default(), - user_id: Default::default(), - }) - }); - - let contexts = disguise_context_mocks(mock_contexts); - let services = disguise_service_mocks(mock_services); - let session_logic = SessionController::new(contexts, services); - - let mut request = Request::new(()); - request.metadata_mut().insert( - "authorization", - metadata::MetadataValue::from_str("Bearer test_token").unwrap(), - ); - - let res = session_logic.delete_session(request).await; - - assert!(res.is_ok()); -} - -#[tokio::test] -async fn delete_session_no_session_returns_err() { - let mut mock_contexts = get_mock_contexts(); - let mock_services = get_mock_services(); - - mock_contexts - .session_context_mock - .expect_delete_by_token() - .with( - predicate::eq(TokenType::AccessToken), - predicate::eq("test_token".to_string()), - ) - .returning(move |_, _| { - Err(DbErr::RecordNotFound( - "No session found with the provided access token".to_string(), - )) - }); - - let contexts = disguise_context_mocks(mock_contexts); - let services = disguise_service_mocks(mock_services); - let session_logic = SessionController::new(contexts, services); - - let mut request = Request::new(()); - request.metadata_mut().insert( - "authorization", - metadata::MetadataValue::from_str("Bearer test_token").unwrap(), - ); - - let res = session_logic.delete_session(request).await; - - assert_eq!(res.unwrap_err().code(), Code::Internal); -} diff --git a/src/tests/controllers/user_controller.rs b/src/tests/controllers/user_controller.rs deleted file mode 100644 index 8d8c6b4..0000000 --- a/src/tests/controllers/user_controller.rs +++ /dev/null @@ -1,412 +0,0 @@ -use crate::api::server::protobuf::{CreateUserRequest, GetUsersRequest, UpdateUserRequest}; -use crate::controllers::controller_impls::UserController; -use crate::controllers::controller_traits::UserControllerTrait; -use crate::entities::user; -use crate::tests::controllers::helpers::{ - disguise_context_mocks, disguise_service_mocks, get_mock_contexts, get_mock_services, -}; -use mockall::predicate; -use sea_orm::DbErr; -use std::str::FromStr; -use tonic::{metadata, Code, Request}; - -#[tokio::test] -async fn delete_user_nonexistent_user_returns_err() { - let mut mock_contexts = get_mock_contexts(); - let mock_services = get_mock_services(); - - mock_contexts - .user_context_mock - .expect_delete() - .with(predicate::eq(1)) - .returning(|_| Err(DbErr::RecordNotFound("".into()))); - - let contexts = disguise_context_mocks(mock_contexts); - let services = disguise_service_mocks(mock_services); - let user_logic = UserController::new(contexts, services); - - let mut delete_request = Request::new(()); - - // Insert uid into request metadata - delete_request - .metadata_mut() - .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); - - let delete_response = user_logic.delete_user(delete_request).await.unwrap_err(); - let expected_response_code = Code::Internal; - - assert_eq!(delete_response.code(), expected_response_code); -} - -#[tokio::test] -async fn delete_user_existing_user_returns_ok() { - let mut mock_contexts = get_mock_contexts(); - let mock_services = get_mock_services(); - - let user = user::Model { - id: 1, - email: "".to_string(), - username: "".to_string(), - password: "".to_string(), - }; - - mock_contexts - .user_context_mock - .expect_delete() - .with(predicate::eq(1)) - .returning(move |_| Ok(user.clone())); - - let contexts = disguise_context_mocks(mock_contexts); - let services = disguise_service_mocks(mock_services); - let user_logic = UserController::new(contexts, services); - - let mut delete_request = Request::new(()); - - // Insert uid into request metadata - delete_request - .metadata_mut() - .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); - - let delete_response = user_logic.delete_user(delete_request).await; - - assert!(delete_response.is_ok()); -} - -#[tokio::test] -async fn create_user_nonexistent_user_returns_ok() { - let mut mock_contexts = get_mock_contexts(); - let mut mock_services = get_mock_services(); - - let password = "Password123".to_string(); - - let user = user::Model { - id: Default::default(), - email: "anders21@student.aau.dk".to_string(), - username: "anders".to_string(), - password: password.clone(), - }; - - let create_user_request = Request::new(CreateUserRequest { - email: "anders21@student.aau.dk".to_string(), - username: "anders".to_string(), - password: password.clone(), - }); - - mock_services - .hashing_service_mock - .expect_hash_password() - .returning(move |_| Ok(password.clone())); - - mock_contexts - .user_context_mock - .expect_create() - .with(predicate::eq(user.clone())) - .returning(move |_| Ok(user.clone())); - - let contexts = disguise_context_mocks(mock_contexts); - let services = disguise_service_mocks(mock_services); - let user_logic = UserController::new(contexts, services); - - let create_user_response = user_logic.create_user(create_user_request).await; - assert!(create_user_response.is_ok()); -} - -#[tokio::test] -async fn create_user_duplicate_email_returns_error() { - let mut mock_contexts = get_mock_contexts(); - let mut mock_services = get_mock_services(); - - let password = "Password123".to_string(); - - let user = user::Model { - id: Default::default(), - email: "anders21@student.aau.dk".to_string(), - username: "anders".to_string(), - password: password.clone(), - }; - - let create_user_request = Request::new(CreateUserRequest { - email: "anders21@student.aau.dk".to_string(), - username: "anders".to_string(), - password: password.clone(), - }); - - mock_services - .hashing_service_mock - .expect_hash_password() - .returning(move |_| Ok(password.clone())); - - mock_contexts - .user_context_mock - .expect_create() - .with(predicate::eq(user.clone())) - .returning(move |_| Err(DbErr::RecordNotInserted)); //todo!("Needs to be a SqlError with UniqueConstraintViolation with 'email' in message) - - let contexts = disguise_context_mocks(mock_contexts); - let services = disguise_service_mocks(mock_services); - let user_logic = UserController::new(contexts, services); - - let res = user_logic.create_user(create_user_request).await; - assert_eq!(res.unwrap_err().code(), Code::Internal); //todo!("Needs to be code AlreadyExists when mocked Error is corrected) -} - -#[tokio::test] -async fn create_user_invalid_email_returns_error() { - let mock_contexts = get_mock_contexts(); - let mock_services = get_mock_services(); - - let contexts = disguise_context_mocks(mock_contexts); - let services = disguise_service_mocks(mock_services); - let user_logic = UserController::new(contexts, services); - - let create_user_request = Request::new(CreateUserRequest { - email: "invalid-email".to_string(), - username: "newuser".to_string(), - password: "123".to_string(), - }); - - let res = user_logic.create_user(create_user_request).await; - assert_eq!(res.unwrap_err().code(), Code::InvalidArgument); -} - -#[tokio::test] -async fn create_user_duplicate_username_returns_error() { - let mut mock_contexts = get_mock_contexts(); - let mut mock_services = get_mock_services(); - - let password = "Password123".to_string(); - - let user = user::Model { - id: Default::default(), - email: "anders21@student.aau.dk".to_string(), - username: "anders".to_string(), - password: password.clone(), - }; - - let create_user_request = Request::new(CreateUserRequest { - email: "anders21@student.aau.dk".to_string(), - username: "anders".to_string(), - password: password.clone(), - }); - - mock_services - .hashing_service_mock - .expect_hash_password() - .returning(move |_| Ok(password.clone())); - - mock_contexts - .user_context_mock - .expect_create() - .with(predicate::eq(user.clone())) - .returning(move |_| Err(DbErr::RecordNotInserted)); //todo!("Needs to be a SqlError with UniqueConstraintViolation with 'username' in message) - - let contexts = disguise_context_mocks(mock_contexts); - let services = disguise_service_mocks(mock_services); - let user_logic = UserController::new(contexts, services); - - let res = user_logic.create_user(create_user_request).await; - assert_eq!(res.unwrap_err().code(), Code::Internal); //todo!("Needs to be code AlreadyExists when mocked Error is corrected) -} - -#[tokio::test] -async fn create_user_invalid_username_returns_error() { - let mock_contexts = get_mock_contexts(); - let mock_services = get_mock_services(); - - let contexts = disguise_context_mocks(mock_contexts); - let services = disguise_service_mocks(mock_services); - let user_logic = UserController::new(contexts, services); - - let create_user_request = Request::new(CreateUserRequest { - email: "valid@email.com".to_string(), - username: "ØØØØØ".to_string(), - password: "123".to_string(), - }); - - let res = user_logic.create_user(create_user_request).await; - assert_eq!(res.unwrap_err().code(), Code::InvalidArgument); -} - -#[tokio::test] -async fn create_user_valid_request_returns_ok() { - let mut mock_contexts = get_mock_contexts(); - let mut mock_services = get_mock_services(); - - let password = "Password123".to_string(); - - let user = user::Model { - id: Default::default(), - email: "newuser@example.com".to_string(), - username: "newuser".to_string(), - password: password.clone(), - }; - - let create_user_request = Request::new(CreateUserRequest { - email: "newuser@example.com".to_string(), - username: "newuser".to_string(), - password: password.clone(), - }); - - mock_services - .hashing_service_mock - .expect_hash_password() - .returning(move |_| Ok(password.clone())); - - mock_contexts - .user_context_mock - .expect_create() - .with(predicate::eq(user.clone())) - .returning(move |_| Ok(user.clone())); - - let contexts = disguise_context_mocks(mock_contexts); - let services = disguise_service_mocks(mock_services); - let user_logic = UserController::new(contexts, services); - - let create_user_response = user_logic.create_user(create_user_request).await; - assert!(create_user_response.is_ok()); -} - -#[tokio::test] -async fn update_user_returns_ok() { - let mut mock_contexts = get_mock_contexts(); - let mut mock_services = get_mock_services(); - - let old_user = user::Model { - id: 1, - email: "olduser@example.com".to_string(), - username: "old_username".to_string(), - password: "StrongPassword123".to_string(), - }; - - let new_user = user::Model { - id: 1, - email: "newuser@example.com".to_string(), - username: "new_username".to_string(), - password: "g76df2gd7hd837g8hjd8723hd8gd823d82d3".to_string(), - }; - - mock_contexts - .user_context_mock - .expect_get_by_id() - .with(predicate::eq(1)) - .returning(move |_| Ok(Some(old_user.clone()))); - - mock_services - .hashing_service_mock - .expect_hash_password() - .with(predicate::eq("StrongPassword123".to_string())) - .returning(move |_| Ok("g76df2gd7hd837g8hjd8723hd8gd823d82d3".to_string())); - - mock_contexts - .user_context_mock - .expect_update() - .with(predicate::eq(new_user.clone())) - .returning(move |_| Ok(new_user.clone())); - - let contexts = disguise_context_mocks(mock_contexts); - let services = disguise_service_mocks(mock_services); - let user_logic = UserController::new(contexts, services); - - let mut update_user_request = Request::new(UpdateUserRequest { - email: Some("newuser@example.com".to_string()), - username: Some("new_username".to_string()), - password: Some("StrongPassword123".to_string()), - }); - - update_user_request - .metadata_mut() - .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); - - let update_user_response = user_logic.update_user(update_user_request).await; - - assert!(update_user_response.is_ok()) -} - -#[tokio::test] -async fn update_user_non_existant_user_returns_err() { - let mut mock_contexts = get_mock_contexts(); - let mock_services = get_mock_services(); - - mock_contexts - .user_context_mock - .expect_get_by_id() - .with(predicate::eq(1)) - .returning(move |_| Err(DbErr::RecordNotFound("".to_string()))); - - let contexts = disguise_context_mocks(mock_contexts); - let services = disguise_service_mocks(mock_services); - let user_logic = UserController::new(contexts, services); - - let mut update_user_request = Request::new(UpdateUserRequest { - email: Some("new_test@test".to_string()), - username: Some("new_test_user".to_string()), - password: Some("new_test_pass".to_string()), - }); - - update_user_request - .metadata_mut() - .insert("uid", metadata::MetadataValue::from_str("1").unwrap()); - - let res = user_logic.update_user(update_user_request).await; - - assert_eq!(res.unwrap_err().code(), Code::Internal); -} - -#[tokio::test] -async fn get_users_returns_ok() { - let mut mock_contexts = get_mock_contexts(); - let mock_services = get_mock_services(); - - let users = vec![ - user::Model { - id: 1, - email: "".to_string(), - username: "".to_string(), - password: "".to_string(), - }, - user::Model { - id: 2, - email: "".to_string(), - username: "".to_string(), - password: "".to_string(), - }, - ]; - - mock_contexts - .user_context_mock - .expect_get_by_ids() - .returning(move |_| Ok(users.clone())); - - let contexts = disguise_context_mocks(mock_contexts); - let services = disguise_service_mocks(mock_services); - let user_logic = UserController::new(contexts, services); - - let get_users_request = Request::new(GetUsersRequest { ids: vec![1, 2] }); - - let get_users_response = user_logic.get_users(get_users_request).await.unwrap(); - - assert_eq!(get_users_response.get_ref().users.len(), 2); -} - -#[tokio::test] -async fn get_users_returns_empty_array() { - let mut mock_contexts = get_mock_contexts(); - let mock_services = get_mock_services(); - - let users: Vec = vec![]; - - mock_contexts - .user_context_mock - .expect_get_by_ids() - .returning(move |_| Ok(users.clone())); - - let contexts = disguise_context_mocks(mock_contexts); - let services = disguise_service_mocks(mock_services); - let user_logic = UserController::new(contexts, services); - - let get_users_request = Request::new(GetUsersRequest { ids: vec![1, 2] }); - - let get_users_response = user_logic.get_users(get_users_request).await.unwrap(); - - assert_eq!(get_users_response.get_ref().users.len(), 0); -} diff --git a/src/tests/mod.rs b/src/tests/mod.rs deleted file mode 100644 index 4274654..0000000 --- a/src/tests/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod api; -pub mod contexts; -pub mod controllers; -pub mod services; diff --git a/src/tests/services/mod.rs b/src/tests/services/mod.rs deleted file mode 100644 index e53208c..0000000 --- a/src/tests/services/mod.rs +++ /dev/null @@ -1 +0,0 @@ -mod reveaal_service; diff --git a/src/tests/services/reveaal_service.rs b/src/tests/services/reveaal_service.rs deleted file mode 100644 index 7d64803..0000000 --- a/src/tests/services/reveaal_service.rs +++ /dev/null @@ -1,29 +0,0 @@ -// use crate::api::server::server::QueryResponse; -// use wiremock_grpc::generate; -// use wiremock_grpc::*; -// -// generate!("EcdarBackend", MyMockServer); - -#[ignore] -#[tokio::test] -async fn send_query_test_correct_query_returns_ok() { - //todo!("Somehow QueryResponse does not implement prost::message::Message even though it does. - // supposedly a versioning error between wiremock_grpc, tonic, and prost") - - // let mut server = MyMockServer::start_default().await; - // - // let request1 = server.setup( - // MockBuilder::when() - // .path("EcdarBackend/SendQuery") - // .then() - // .return_status(Code::Ok) - // .return_body(|| QueryResponse { - // query_id: 0, - // info: vec![], - // result: None, - // }), - // ); - - //... - //https://crates.io/crates/wiremock-grpc -} From f28e077c1b1754574202c4c35b60e06701eb55c5 Mon Sep 17 00:00:00 2001 From: Thomas Lohse Date: Mon, 26 Feb 2024 11:18:55 +0100 Subject: [PATCH 2/6] refactored trait/impl directories --- src/api/auth.rs | 53 +---- src/api/ecdar_api.rs | 2 +- src/api/server.rs | 2 +- .../{context_impls => }/access_context.rs | 27 ++- src/contexts/context_collection.rs | 2 +- src/contexts/context_impls/mod.rs | 17 -- .../context_traits/access_context_trait.rs | 20 -- .../context_traits/in_use_context_trait.rs | 4 - src/contexts/context_traits/mod.rs | 17 -- .../context_traits/project_context_trait.rs | 13 -- .../context_traits/query_context_trait.rs | 10 - .../context_traits/session_context_trait.rs | 26 --- .../context_traits/user_context_trait.rs | 28 --- .../database_context_trait.rs | 0 src/contexts/db_centexts/mod.rs | 7 + .../postgres_database_context.rs | 3 +- .../sqlite_database_context.rs | 2 +- .../entity_context_trait.rs | 0 .../{context_impls => }/in_use_context.rs | 12 +- src/contexts/mod.rs | 25 ++- .../{context_impls => }/project_context.rs | 19 +- .../{context_impls => }/query_context.rs | 17 +- .../{context_impls => }/session_context.rs | 32 ++- .../{context_impls => }/user_context.rs | 31 ++- .../access_controller.rs | 49 +++- src/controllers/controller_collection.rs | 2 +- src/controllers/controller_impls/mod.rs | 209 ----------------- .../access_controller_trait.rs | 45 ---- src/controllers/controller_traits/mod.rs | 11 - .../project_controller_trait.rs | 51 ----- .../query_controller_trait.rs | 41 ---- .../session_controller_trait.rs | 22 -- .../user_controller_trait.rs | 22 -- src/controllers/mod.rs | 210 +++++++++++++++++- .../project_controller.rs | 52 ++++- .../query_controller.rs | 45 +++- .../reveaal_controller.rs | 2 +- .../session_controller.rs | 28 ++- .../{controller_impls => }/user_controller.rs | 26 ++- src/main.rs | 11 +- .../{service_impls => }/hashing_service.rs | 6 +- src/services/mod.rs | 10 +- .../{service_impls => }/reveaal_service.rs | 21 +- src/services/service_collection.rs | 2 +- src/services/service_impls/mod.rs | 5 - .../service_traits/hashing_service_trait.rs | 6 - src/services/service_traits/mod.rs | 5 - .../service_traits/reveaal_service_trait.rs | 26 --- 48 files changed, 566 insertions(+), 710 deletions(-) rename src/contexts/{context_impls => }/access_context.rs (94%) delete mode 100644 src/contexts/context_impls/mod.rs delete mode 100644 src/contexts/context_traits/access_context_trait.rs delete mode 100644 src/contexts/context_traits/in_use_context_trait.rs delete mode 100644 src/contexts/context_traits/mod.rs delete mode 100644 src/contexts/context_traits/project_context_trait.rs delete mode 100644 src/contexts/context_traits/query_context_trait.rs delete mode 100644 src/contexts/context_traits/session_context_trait.rs delete mode 100644 src/contexts/context_traits/user_context_trait.rs rename src/contexts/{context_traits => db_centexts}/database_context_trait.rs (100%) create mode 100644 src/contexts/db_centexts/mod.rs rename src/contexts/{context_impls => db_centexts}/postgres_database_context.rs (94%) rename src/contexts/{context_impls => db_centexts}/sqlite_database_context.rs (95%) rename src/contexts/{context_traits => }/entity_context_trait.rs (100%) rename src/contexts/{context_impls => }/in_use_context.rs (97%) rename src/contexts/{context_impls => }/project_context.rs (97%) rename src/contexts/{context_impls => }/query_context.rs (96%) rename src/contexts/{context_impls => }/session_context.rs (95%) rename src/contexts/{context_impls => }/user_context.rs (93%) rename src/controllers/{controller_impls => }/access_controller.rs (94%) delete mode 100644 src/controllers/controller_impls/mod.rs delete mode 100644 src/controllers/controller_traits/access_controller_trait.rs delete mode 100644 src/controllers/controller_traits/mod.rs delete mode 100644 src/controllers/controller_traits/project_controller_trait.rs delete mode 100644 src/controllers/controller_traits/query_controller_trait.rs delete mode 100644 src/controllers/controller_traits/session_controller_trait.rs delete mode 100644 src/controllers/controller_traits/user_controller_trait.rs rename src/controllers/{controller_impls => }/project_controller.rs (97%) rename src/controllers/{controller_impls => }/query_controller.rs (95%) rename src/controllers/{controller_impls => }/reveaal_controller.rs (96%) rename src/controllers/{controller_impls => }/session_controller.rs (95%) rename src/controllers/{controller_impls => }/user_controller.rs (96%) rename src/services/{service_impls => }/hashing_service.rs (63%) rename src/services/{service_impls => }/reveaal_service.rs (81%) delete mode 100644 src/services/service_impls/mod.rs delete mode 100644 src/services/service_traits/hashing_service_trait.rs delete mode 100644 src/services/service_traits/mod.rs delete mode 100644 src/services/service_traits/reveaal_service_trait.rs diff --git a/src/api/auth.rs b/src/api/auth.rs index 4d80f28..d9eebc0 100644 --- a/src/api/auth.rs +++ b/src/api/auth.rs @@ -18,7 +18,7 @@ pub fn validation_interceptor(mut req: Request<()>) -> Result, Statu err )) })? { - Some(token) => Token::from_str(TokenType::AccessToken, &token), + Some(token) => Token::from_str(TokenType::AccessToken, token), None => return Err(Status::unauthenticated("Token not found")), }; @@ -60,7 +60,6 @@ impl TokenType { /// /// # Panics /// This method will panic if the token secret environment variable is not set. - #[allow(clippy::expect_used)] fn secret(&self) -> String { match self { TokenType::AccessToken => env::var("ACCESS_TOKEN_HS512_SECRET") @@ -174,10 +173,10 @@ impl Token { /// /// let token = Token::from_str(TokenType::AccessToken, "token") /// ``` - pub fn from_str(token_type: TokenType, token: &str) -> Token { + pub fn from_str>(token_type: TokenType, token: T) -> Token { Token { token_type, - token: token.to_string(), + token: token.into(), } } /// Validate the token. Returns the token data if the token is valid. @@ -205,34 +204,6 @@ impl Token { Err(err) => Err(err.into()), } } - /// # Examples - /// - /// ``` - /// use ecdar_api::controllers::auth::{Token, TokenType}; - /// - /// let token = Token::from_str(TokenType::AccessToken, "token"); - /// - /// assert_eq!(token.as_str(), "token"); - /// ``` - #[allow(dead_code)] - pub fn as_str(&self) -> &str { - &self.token - } - /// Returns the token type. - /// - /// # Examples - /// - /// ``` - /// use ecdar_api::controllers::auth::{Token, TokenType}; - /// - /// let token = Token::new(TokenType::AccessToken, "1").unwrap(); - /// - /// assert_eq!(token.token_type(), TokenType::AccessToken); - /// ``` - #[allow(dead_code)] - pub fn token_type(&self) -> TokenType { - self.token_type.clone() - } } impl Display for Token { @@ -404,7 +375,7 @@ mod tests { env::set_var("ACCESS_TOKEN_HS512_SECRET", "access_secret"); let token = Token::new(TokenType::AccessToken, "1").unwrap(); - let result = token.token_type(); + let result = token.token_type; assert_eq!(result, TokenType::AccessToken); } @@ -414,7 +385,7 @@ mod tests { env::set_var("REFRESH_TOKEN_HS512_SECRET", "refresh_secret"); let token = Token::new(TokenType::RefreshToken, "1").unwrap(); - let result = token.token_type(); + let result = token.token_type; assert_eq!(result, TokenType::RefreshToken); } @@ -426,17 +397,7 @@ mod tests { let token = Token::new(TokenType::AccessToken, "1").unwrap(); let result = token.to_string(); - assert_eq!(result, token.as_str()); - } - - #[tokio::test] - async fn token_as_str_returns_string() { - env::set_var("ACCESS_TOKEN_HS512_SECRET", "access_secret"); - - let token = Token::new(TokenType::AccessToken, "1").unwrap(); - let result = token.as_str(); - - assert_eq!(result, token.to_string()); + assert_eq!(result, token.token); } #[tokio::test] @@ -444,7 +405,7 @@ mod tests { env::set_var("ACCESS_TOKEN_HS512_SECRET", "access_secret"); let token = Token::new(TokenType::AccessToken, "1").unwrap(); - let token_from_str = Token::from_str(TokenType::AccessToken, token.as_str()); + let token_from_str = Token::from_str(TokenType::AccessToken, token.token); let result = token_from_str.validate(); diff --git a/src/api/ecdar_api.rs b/src/api/ecdar_api.rs index 8c78e38..4836753 100644 --- a/src/api/ecdar_api.rs +++ b/src/api/ecdar_api.rs @@ -1,4 +1,4 @@ -use crate::controllers::controller_collection::ControllerCollection; +use crate::controllers::ControllerCollection; /// The collection of all controllers that Ecdar API offers. #[derive(Clone)] diff --git a/src/api/server.rs b/src/api/server.rs index 316a834..5b7af62 100644 --- a/src/api/server.rs +++ b/src/api/server.rs @@ -7,7 +7,7 @@ use crate::api::ecdar_api::ConcreteEcdarApi; use crate::api::server::protobuf::ecdar_api_auth_server::EcdarApiAuthServer; use crate::api::server::protobuf::ecdar_api_server::EcdarApiServer; use crate::api::server::protobuf::ecdar_backend_server::EcdarBackendServer; -use crate::controllers::controller_collection::ControllerCollection; +use crate::controllers::ControllerCollection; pub mod protobuf { tonic::include_proto!("ecdar_proto_buf"); diff --git a/src/contexts/context_impls/access_context.rs b/src/contexts/access_context.rs similarity index 94% rename from src/contexts/context_impls/access_context.rs rename to src/contexts/access_context.rs index 2a45dc6..f2d74e8 100644 --- a/src/contexts/context_impls/access_context.rs +++ b/src/contexts/access_context.rs @@ -1,13 +1,27 @@ use crate::api::server::protobuf::AccessInfo; -use crate::contexts::context_traits::{ - AccessContextTrait, DatabaseContextTrait, EntityContextTrait, -}; +use crate::contexts::db_centexts::DatabaseContextTrait; +use crate::contexts::EntityContextTrait; use crate::entities::access; use sea_orm::prelude::async_trait::async_trait; use sea_orm::ActiveValue::{Set, Unchanged}; use sea_orm::{ActiveModelTrait, ColumnTrait, Condition, DbErr, EntityTrait, QueryFilter}; use std::sync::Arc; +#[async_trait] +pub trait AccessContextTrait: EntityContextTrait { + /// Searches for an access entity by `User` and `Project` id, + /// returning [`Some`] if any entity was found, [`None`] otherwise + /// # Errors + /// Errors on failed connection, execution error or constraint violations. + async fn get_access_by_uid_and_project_id( + &self, + uid: i32, + project_id: i32, + ) -> Result, DbErr>; + /// Returns all [`access::Model`] that are associated with a given `Project`` + async fn get_access_by_project_id(&self, project_id: i32) -> Result, DbErr>; +} + pub struct AccessContext { db_context: Arc, } @@ -134,13 +148,14 @@ impl EntityContextTrait for AccessContext { } #[cfg(test)] mod tests { - use super::super::super::helpers::{ + use super::super::helpers::{ create_accesses, create_projects, create_users, get_reset_database_context, }; use crate::api::server::protobuf::AccessInfo; - use crate::contexts::context_traits::{AccessContextTrait, EntityContextTrait}; + use crate::contexts::access_context::AccessContextTrait; + use crate::contexts::EntityContextTrait; use crate::{ - contexts::context_impls::AccessContext, + contexts::AccessContext, entities::{access, project, user}, to_active_models, }; diff --git a/src/contexts/context_collection.rs b/src/contexts/context_collection.rs index 615ea8f..37c8f3f 100644 --- a/src/contexts/context_collection.rs +++ b/src/contexts/context_collection.rs @@ -1,4 +1,4 @@ -use crate::contexts::context_traits::*; +use crate::contexts::*; use std::sync::Arc; #[derive(Clone)] diff --git a/src/contexts/context_impls/mod.rs b/src/contexts/context_impls/mod.rs deleted file mode 100644 index e0b0fcb..0000000 --- a/src/contexts/context_impls/mod.rs +++ /dev/null @@ -1,17 +0,0 @@ -pub mod access_context; -pub mod in_use_context; -pub mod postgres_database_context; -pub mod project_context; -pub mod query_context; -pub mod session_context; -pub mod sqlite_database_context; -pub mod user_context; - -pub use access_context::AccessContext; -pub use in_use_context::InUseContext; -pub use postgres_database_context::PostgresDatabaseContext; -pub use project_context::ProjectContext; -pub use query_context::QueryContext; -pub use session_context::SessionContext; -pub use sqlite_database_context::SQLiteDatabaseContext; -pub use user_context::UserContext; diff --git a/src/contexts/context_traits/access_context_trait.rs b/src/contexts/context_traits/access_context_trait.rs deleted file mode 100644 index 36d08e0..0000000 --- a/src/contexts/context_traits/access_context_trait.rs +++ /dev/null @@ -1,20 +0,0 @@ -use crate::api::server::protobuf::AccessInfo; -use crate::contexts::context_traits::EntityContextTrait; -use crate::entities::access; -use async_trait::async_trait; -use sea_orm::DbErr; - -#[async_trait] -pub trait AccessContextTrait: EntityContextTrait { - /// Searches for an access entity by `User` and `Project` id, - /// returning [`Some`] if any entity was found, [`None`] otherwise - /// # Errors - /// Errors on failed connection, execution error or constraint violations. - async fn get_access_by_uid_and_project_id( - &self, - uid: i32, - project_id: i32, - ) -> Result, DbErr>; - /// Returns all [`access::Model`] that are associated with a given `Project`` - async fn get_access_by_project_id(&self, project_id: i32) -> Result, DbErr>; -} diff --git a/src/contexts/context_traits/in_use_context_trait.rs b/src/contexts/context_traits/in_use_context_trait.rs deleted file mode 100644 index 84181ed..0000000 --- a/src/contexts/context_traits/in_use_context_trait.rs +++ /dev/null @@ -1,4 +0,0 @@ -use crate::contexts::context_traits::EntityContextTrait; -use crate::entities::in_use; - -pub trait InUseContextTrait: EntityContextTrait {} diff --git a/src/contexts/context_traits/mod.rs b/src/contexts/context_traits/mod.rs deleted file mode 100644 index a35698e..0000000 --- a/src/contexts/context_traits/mod.rs +++ /dev/null @@ -1,17 +0,0 @@ -pub mod access_context_trait; -pub mod database_context_trait; -pub mod entity_context_trait; -pub mod in_use_context_trait; -pub mod project_context_trait; -pub mod query_context_trait; -pub mod session_context_trait; -pub mod user_context_trait; - -pub use access_context_trait::AccessContextTrait; -pub use database_context_trait::DatabaseContextTrait; -pub use entity_context_trait::EntityContextTrait; -pub use in_use_context_trait::InUseContextTrait; -pub use project_context_trait::ProjectContextTrait; -pub use query_context_trait::QueryContextTrait; -pub use session_context_trait::SessionContextTrait; -pub use user_context_trait::UserContextTrait; diff --git a/src/contexts/context_traits/project_context_trait.rs b/src/contexts/context_traits/project_context_trait.rs deleted file mode 100644 index d6ec6db..0000000 --- a/src/contexts/context_traits/project_context_trait.rs +++ /dev/null @@ -1,13 +0,0 @@ -use crate::api::server::protobuf::ProjectInfo; -use crate::contexts::context_traits::EntityContextTrait; -use crate::entities::project; -use async_trait::async_trait; -use sea_orm::DbErr; - -#[async_trait] -pub trait ProjectContextTrait: EntityContextTrait { - /// Returns the projects owned by a given user id - /// # Errors - /// Errors on failed connection, execution error or constraint violations. - async fn get_project_info_by_uid(&self, uid: i32) -> Result, DbErr>; -} diff --git a/src/contexts/context_traits/query_context_trait.rs b/src/contexts/context_traits/query_context_trait.rs deleted file mode 100644 index ba50e87..0000000 --- a/src/contexts/context_traits/query_context_trait.rs +++ /dev/null @@ -1,10 +0,0 @@ -use crate::contexts::context_traits::EntityContextTrait; -use crate::entities::query; -use async_trait::async_trait; -use sea_orm::DbErr; - -#[async_trait] -pub trait QueryContextTrait: EntityContextTrait { - /// Returns the queries associated with a given project id - async fn get_all_by_project_id(&self, project_id: i32) -> Result, DbErr>; -} diff --git a/src/contexts/context_traits/session_context_trait.rs b/src/contexts/context_traits/session_context_trait.rs deleted file mode 100644 index 5139cea..0000000 --- a/src/contexts/context_traits/session_context_trait.rs +++ /dev/null @@ -1,26 +0,0 @@ -use crate::api::auth::TokenType; -use crate::contexts::context_traits::EntityContextTrait; -use crate::entities::session; -use async_trait::async_trait; -use sea_orm::DbErr; - -#[async_trait] -pub trait SessionContextTrait: EntityContextTrait { - /// Searches for a token by `Access` or `Refresh` token, - /// returning [`Some`] if one is found, [`None`] otherwise - /// # Errors - /// Errors on failed connection, execution error or constraint violations. - async fn get_by_token( - &self, - token_type: TokenType, - token: String, - ) -> Result, DbErr>; - /// Searches for a token by `Access` or `Refresh` token, deleting and returning it - /// # Errors - /// Errors on failed connection, execution error or constraint violations. - async fn delete_by_token( - &self, - token_type: TokenType, - token: String, - ) -> Result; -} diff --git a/src/contexts/context_traits/user_context_trait.rs b/src/contexts/context_traits/user_context_trait.rs deleted file mode 100644 index 8cf410b..0000000 --- a/src/contexts/context_traits/user_context_trait.rs +++ /dev/null @@ -1,28 +0,0 @@ -use crate::contexts::context_traits::EntityContextTrait; -use crate::entities::user; -use async_trait::async_trait; -use sea_orm::DbErr; - -#[async_trait] -pub trait UserContextTrait: EntityContextTrait { - /// Searches for a `User` by username, returning [`Some`] if one is found, [`None`] otherwise. - /// # Errors - /// Errors on failed connection, execution error or constraint violations. - /// # Notes - /// Since usernames are unique, it is guaranteed that at most one user with the given username exists. - async fn get_by_username(&self, username: String) -> Result, DbErr>; - /// Searches for a `User` by email address, returning [`Some`] if one is found, [`None`] otherwise. - /// # Errors - /// Errors on failed connection, execution error or constraint violations. - /// # Notes - /// Since email address' are unique, it is guaranteed that at most one user with the given email address exists. - async fn get_by_email(&self, email: String) -> Result, DbErr>; - /// Returns all the user entities with the given ids - /// # Example - /// ``` - /// let context : UserContext = UserContext::new(...); - /// let model : vec = context.get_by_ids(vec![1,2]).unwrap(); - /// assert_eq!(model.len(),2); - /// ``` - async fn get_by_ids(&self, ids: Vec) -> Result, DbErr>; -} diff --git a/src/contexts/context_traits/database_context_trait.rs b/src/contexts/db_centexts/database_context_trait.rs similarity index 100% rename from src/contexts/context_traits/database_context_trait.rs rename to src/contexts/db_centexts/database_context_trait.rs diff --git a/src/contexts/db_centexts/mod.rs b/src/contexts/db_centexts/mod.rs new file mode 100644 index 0000000..be7e519 --- /dev/null +++ b/src/contexts/db_centexts/mod.rs @@ -0,0 +1,7 @@ +pub mod database_context_trait; +pub mod postgres_database_context; +pub mod sqlite_database_context; + +pub use database_context_trait::DatabaseContextTrait; +pub use postgres_database_context::PostgresDatabaseContext; +pub use sqlite_database_context::SQLiteDatabaseContext; diff --git a/src/contexts/context_impls/postgres_database_context.rs b/src/contexts/db_centexts/postgres_database_context.rs similarity index 94% rename from src/contexts/context_impls/postgres_database_context.rs rename to src/contexts/db_centexts/postgres_database_context.rs index 848bf3e..1e30eb5 100644 --- a/src/contexts/context_impls/postgres_database_context.rs +++ b/src/contexts/db_centexts/postgres_database_context.rs @@ -1,8 +1,9 @@ -use crate::contexts::context_traits::DatabaseContextTrait; +use crate::contexts::db_centexts::DatabaseContextTrait; use async_trait::async_trait; use migration::{Migrator, MigratorTrait}; use sea_orm::{ConnectionTrait, Database, DatabaseConnection, DbBackend, DbErr}; use std::sync::Arc; + #[derive(Debug)] pub struct PostgresDatabaseContext { pub(crate) db_connection: DatabaseConnection, diff --git a/src/contexts/context_impls/sqlite_database_context.rs b/src/contexts/db_centexts/sqlite_database_context.rs similarity index 95% rename from src/contexts/context_impls/sqlite_database_context.rs rename to src/contexts/db_centexts/sqlite_database_context.rs index 62719c1..a0e29ed 100644 --- a/src/contexts/context_impls/sqlite_database_context.rs +++ b/src/contexts/db_centexts/sqlite_database_context.rs @@ -1,4 +1,4 @@ -use crate::contexts::context_traits::DatabaseContextTrait; +use crate::contexts::db_centexts::DatabaseContextTrait; use migration::{Migrator, MigratorTrait}; use sea_orm::prelude::async_trait::async_trait; use sea_orm::{ConnectionTrait, Database, DatabaseConnection, DbBackend, DbErr}; diff --git a/src/contexts/context_traits/entity_context_trait.rs b/src/contexts/entity_context_trait.rs similarity index 100% rename from src/contexts/context_traits/entity_context_trait.rs rename to src/contexts/entity_context_trait.rs diff --git a/src/contexts/context_impls/in_use_context.rs b/src/contexts/in_use_context.rs similarity index 97% rename from src/contexts/context_impls/in_use_context.rs rename to src/contexts/in_use_context.rs index 7c5ddbb..49e0185 100644 --- a/src/contexts/context_impls/in_use_context.rs +++ b/src/contexts/in_use_context.rs @@ -1,12 +1,12 @@ -use crate::contexts::context_traits::{ - DatabaseContextTrait, EntityContextTrait, InUseContextTrait, -}; +use crate::contexts::{db_centexts::DatabaseContextTrait, EntityContextTrait}; use crate::entities::in_use; use async_trait::async_trait; use chrono::Utc; use sea_orm::{ActiveModelTrait, DbErr, EntityTrait, Set, Unchanged}; use std::sync::Arc; +pub trait InUseContextTrait: EntityContextTrait {} + pub struct InUseContext { db_context: Arc, } @@ -69,10 +69,10 @@ impl EntityContextTrait for InUseContext { #[cfg(test)] mod tests { - use super::super::super::helpers::*; + use super::super::helpers::*; use crate::{ - contexts::context_impls::InUseContext, - contexts::context_traits::EntityContextTrait, + contexts::EntityContextTrait, + contexts::InUseContext, entities::{in_use, project, session, user}, to_active_models, }; diff --git a/src/contexts/mod.rs b/src/contexts/mod.rs index 40a2112..b01b91f 100644 --- a/src/contexts/mod.rs +++ b/src/contexts/mod.rs @@ -1,11 +1,26 @@ -pub mod context_collection; -pub mod context_impls; -pub mod context_traits; +mod access_context; +mod context_collection; +mod db_centexts; +mod entity_context_trait; +mod in_use_context; +mod project_context; +mod query_context; +mod session_context; +mod user_context; + +pub use access_context::*; +pub use context_collection::*; +pub use db_centexts::*; +pub use entity_context_trait::*; +pub use in_use_context::*; +pub use project_context::*; +pub use query_context::*; +pub use session_context::*; +pub use user_context::*; #[cfg(test)] mod helpers { - use crate::contexts::context_impls::{PostgresDatabaseContext, SQLiteDatabaseContext}; - use crate::contexts::context_traits::DatabaseContextTrait; + use crate::contexts::*; use crate::entities::{access, in_use, project, query, session, user}; use dotenv::dotenv; use sea_orm::{ConnectionTrait, Database, DbBackend}; diff --git a/src/contexts/context_impls/project_context.rs b/src/contexts/project_context.rs similarity index 97% rename from src/contexts/context_impls/project_context.rs rename to src/contexts/project_context.rs index eb6019e..71e4f09 100644 --- a/src/contexts/context_impls/project_context.rs +++ b/src/contexts/project_context.rs @@ -1,9 +1,8 @@ -use crate::contexts::context_traits::{ - DatabaseContextTrait, EntityContextTrait, ProjectContextTrait, -}; +use crate::contexts::EntityContextTrait; use crate::entities::{access, project, query}; use crate::api::server::protobuf::ProjectInfo; +use crate::contexts::db_centexts::DatabaseContextTrait; use async_trait::async_trait; use sea_orm::{ ActiveModelTrait, ColumnTrait, DbErr, EntityTrait, IntoActiveModel, JoinType, ModelTrait, @@ -11,6 +10,14 @@ use sea_orm::{ }; use std::sync::Arc; +#[async_trait] +pub trait ProjectContextTrait: EntityContextTrait { + /// Returns the projects owned by a given user id + /// # Errors + /// Errors on failed connection, execution error or constraint violations. + async fn get_project_info_by_uid(&self, uid: i32) -> Result, DbErr>; +} + pub struct ProjectContext { db_context: Arc, } @@ -151,10 +158,10 @@ impl EntityContextTrait for ProjectContext { #[cfg(test)] mod tests { - use super::super::super::helpers::*; + use super::super::helpers::*; use crate::{ - contexts::context_impls::ProjectContext, - contexts::context_traits::EntityContextTrait, + contexts::EntityContextTrait, + contexts::ProjectContext, entities::{access, in_use, project, query, session, user}, to_active_models, }; diff --git a/src/contexts/context_impls/query_context.rs b/src/contexts/query_context.rs similarity index 96% rename from src/contexts/context_impls/query_context.rs rename to src/contexts/query_context.rs index bfd662b..49f0199 100644 --- a/src/contexts/context_impls/query_context.rs +++ b/src/contexts/query_context.rs @@ -1,12 +1,17 @@ -use crate::contexts::context_traits::{ - DatabaseContextTrait, EntityContextTrait, QueryContextTrait, -}; +use crate::contexts::db_centexts::DatabaseContextTrait; +use crate::contexts::EntityContextTrait; use crate::entities::query; use sea_orm::prelude::async_trait::async_trait; use sea_orm::ActiveValue::{Set, Unchanged}; use sea_orm::{ActiveModelTrait, ColumnTrait, DbErr, EntityTrait, NotSet, QueryFilter}; use std::sync::Arc; +#[async_trait] +pub trait QueryContextTrait: EntityContextTrait { + /// Returns the queries associated with a given project id + async fn get_all_by_project_id(&self, project_id: i32) -> Result, DbErr>; +} + pub struct QueryContext { db_context: Arc, } @@ -129,12 +134,12 @@ impl EntityContextTrait for QueryContext { #[cfg(test)] mod tests { - use super::super::super::helpers::{ + use super::super::helpers::{ create_projects, create_queries, create_users, get_reset_database_context, }; use crate::{ - contexts::context_impls::QueryContext, - contexts::context_traits::EntityContextTrait, + contexts::EntityContextTrait, + contexts::QueryContext, entities::{project, query, user}, to_active_models, }; diff --git a/src/contexts/context_impls/session_context.rs b/src/contexts/session_context.rs similarity index 95% rename from src/contexts/context_impls/session_context.rs rename to src/contexts/session_context.rs index 6238722..d8b5892 100644 --- a/src/contexts/context_impls/session_context.rs +++ b/src/contexts/session_context.rs @@ -1,7 +1,6 @@ use crate::api::auth::TokenType; -use crate::contexts::context_traits::{ - DatabaseContextTrait, EntityContextTrait, SessionContextTrait, -}; +use crate::contexts::db_centexts::DatabaseContextTrait; +use crate::contexts::EntityContextTrait; use crate::entities::session; use chrono::Local; use sea_orm::prelude::async_trait::async_trait; @@ -9,6 +8,27 @@ use sea_orm::ActiveValue::{Set, Unchanged}; use sea_orm::{ActiveModelTrait, ColumnTrait, DbErr, EntityTrait, NotSet, QueryFilter}; use std::sync::Arc; +#[async_trait] +pub trait SessionContextTrait: EntityContextTrait { + /// Searches for a token by `Access` or `Refresh` token, + /// returning [`Some`] if one is found, [`None`] otherwise + /// # Errors + /// Errors on failed connection, execution error or constraint violations. + async fn get_by_token( + &self, + token_type: TokenType, + token: String, + ) -> Result, DbErr>; + /// Searches for a token by `Access` or `Refresh` token, deleting and returning it + /// # Errors + /// Errors on failed connection, execution error or constraint violations. + async fn delete_by_token( + &self, + token_type: TokenType, + token: String, + ) -> Result; +} + pub struct SessionContext { db_context: Arc, } @@ -179,14 +199,14 @@ impl EntityContextTrait for SessionContext { #[cfg(test)] mod tests { - use super::super::super::helpers::*; + use super::super::helpers::*; use crate::api::auth::TokenType; use sea_orm::{entity::prelude::*, IntoActiveModel}; use std::ops::Add; use crate::{ - contexts::context_impls::SessionContext, - contexts::context_traits::{EntityContextTrait, SessionContextTrait}, + contexts::SessionContext, + contexts::{EntityContextTrait, SessionContextTrait}, entities::{in_use, project, session, user}, to_active_models, }; diff --git a/src/contexts/context_impls/user_context.rs b/src/contexts/user_context.rs similarity index 93% rename from src/contexts/context_impls/user_context.rs rename to src/contexts/user_context.rs index 9465c22..9b34bf6 100644 --- a/src/contexts/context_impls/user_context.rs +++ b/src/contexts/user_context.rs @@ -1,10 +1,35 @@ -use crate::contexts::context_traits::{DatabaseContextTrait, EntityContextTrait, UserContextTrait}; +use crate::contexts::db_centexts::DatabaseContextTrait; +use crate::contexts::EntityContextTrait; use crate::entities::user; use sea_orm::prelude::async_trait::async_trait; use sea_orm::ActiveValue::{Set, Unchanged}; use sea_orm::{ActiveModelTrait, ColumnTrait, DbErr, EntityTrait, QueryFilter}; use std::sync::Arc; +#[async_trait] +pub trait UserContextTrait: EntityContextTrait { + /// Searches for a `User` by username, returning [`Some`] if one is found, [`None`] otherwise. + /// # Errors + /// Errors on failed connection, execution error or constraint violations. + /// # Notes + /// Since usernames are unique, it is guaranteed that at most one user with the given username exists. + async fn get_by_username(&self, username: String) -> Result, DbErr>; + /// Searches for a `User` by email address, returning [`Some`] if one is found, [`None`] otherwise. + /// # Errors + /// Errors on failed connection, execution error or constraint violations. + /// # Notes + /// Since email address' are unique, it is guaranteed that at most one user with the given email address exists. + async fn get_by_email(&self, email: String) -> Result, DbErr>; + /// Returns all the user entities with the given ids + /// # Example + /// ``` + /// let context : UserContext = UserContext::new(...); + /// let model : vec = context.get_by_ids(vec![1,2]).unwrap(); + /// assert_eq!(model.len(),2); + /// ``` + async fn get_by_ids(&self, ids: Vec) -> Result, DbErr>; +} + pub struct UserContext { db_context: Arc, } @@ -150,8 +175,8 @@ impl EntityContextTrait for UserContext { mod tests { use crate::contexts::helpers::*; use crate::{ - contexts::context_impls::UserContext, - contexts::context_traits::{EntityContextTrait, UserContextTrait}, + contexts::UserContext, + contexts::{EntityContextTrait, UserContextTrait}, entities::{access, project, session, user}, to_active_models, }; diff --git a/src/controllers/controller_impls/access_controller.rs b/src/controllers/access_controller.rs similarity index 94% rename from src/controllers/controller_impls/access_controller.rs rename to src/controllers/access_controller.rs index 325a749..3863c9f 100644 --- a/src/controllers/controller_impls/access_controller.rs +++ b/src/controllers/access_controller.rs @@ -4,14 +4,53 @@ use crate::api::server::protobuf::{ CreateAccessRequest, DeleteAccessRequest, ListAccessInfoRequest, ListAccessInfoResponse, UpdateAccessRequest, }; -use crate::contexts::context_collection::ContextCollection; -use crate::contexts::context_traits::{AccessContextTrait, UserContextTrait}; -use crate::controllers::controller_traits::AccessControllerTrait; +use crate::contexts::AccessContextTrait; +use crate::contexts::ContextCollection; +use crate::contexts::UserContextTrait; use crate::entities::{access, user}; use async_trait::async_trait; use std::sync::Arc; use tonic::{Code, Request, Response, Status}; +#[async_trait] +pub trait AccessControllerTrait: Send + Sync { + /// handles the list_access_info endpoint + /// # Errors + /// If an invalid or non-existent [`ListAccessInfoRequest::project_id`] is provided + async fn list_access_info( + &self, + request: Request, + ) -> Result, Status>; + /// Creates an access in the contexts. + /// # Errors + /// Returns an error if the contexts context fails to create the access + async fn create_access( + &self, + request: Request, + ) -> Result, Status>; + + /// Endpoint for updating an access record. + /// + /// Takes [`UpdateAccessRequest`] as input + /// + /// Returns a [`Status`] as response + /// + /// `project_id` and `user_id` is set to 'default' since they won't be updated in the contexts. + async fn update_access( + &self, + request: Request, + ) -> Result, Status>; + + /// Deletes the an Access from the contexts. This has no sideeffects. + /// + /// # Errors + /// This function will return an error if the access does not exist in the contexts. + async fn delete_access( + &self, + request: Request, + ) -> Result, Status>; +} + pub struct AccessController { contexts: ContextCollection, } @@ -312,8 +351,8 @@ mod tests { AccessInfo, CreateAccessRequest, DeleteAccessRequest, ListAccessInfoRequest, UpdateAccessRequest, }; - use crate::controllers::controller_impls::AccessController; - use crate::controllers::controller_traits::AccessControllerTrait; + use crate::controllers::AccessController; + use crate::controllers::AccessControllerTrait; use crate::entities::{access, project, user}; use mockall::predicate; use sea_orm::DbErr; diff --git a/src/controllers/controller_collection.rs b/src/controllers/controller_collection.rs index d2374ef..5ffbc95 100644 --- a/src/controllers/controller_collection.rs +++ b/src/controllers/controller_collection.rs @@ -1,5 +1,5 @@ use crate::api::server::protobuf::ecdar_backend_server::EcdarBackend; -use crate::controllers::controller_traits::*; +use crate::controllers::*; use std::sync::Arc; #[derive(Clone)] diff --git a/src/controllers/controller_impls/mod.rs b/src/controllers/controller_impls/mod.rs deleted file mode 100644 index c2c25b8..0000000 --- a/src/controllers/controller_impls/mod.rs +++ /dev/null @@ -1,209 +0,0 @@ -pub mod access_controller; -pub mod project_controller; -pub mod query_controller; -pub mod reveaal_controller; -pub mod session_controller; -pub mod user_controller; - -pub use access_controller::AccessController; -pub use project_controller::ProjectController; -pub use query_controller::QueryController; -pub use reveaal_controller::ReveaalController; -pub use session_controller::SessionController; -pub use user_controller::UserController; - -#[cfg(test)] -mod helpers { - use crate::api::auth::TokenType; - use crate::api::server::protobuf::AccessInfo; - use crate::api::server::protobuf::ProjectInfo; - use crate::api::server::protobuf::{ - QueryRequest, QueryResponse, SimulationStartRequest, SimulationStepRequest, - SimulationStepResponse, UserTokenResponse, - }; - use crate::contexts::context_collection::ContextCollection; - use crate::contexts::context_traits::*; - use crate::entities::{access, in_use, project, query, session, user}; - use crate::services::service_collection::ServiceCollection; - use crate::services::service_traits::*; - use async_trait::async_trait; - use mockall::mock; - use sea_orm::DbErr; - use std::sync::Arc; - use tonic::{Request, Response, Status}; - - pub fn get_mock_contexts() -> MockContexts { - MockContexts { - access_context_mock: MockAccessContext::new(), - in_use_context_mock: MockInUseContext::new(), - project_context_mock: MockProjectContext::new(), - query_context_mock: MockQueryContext::new(), - session_context_mock: MockSessionContext::new(), - user_context_mock: MockUserContext::new(), - } - } - - pub fn get_mock_services() -> MockServices { - MockServices { - hashing_service_mock: MockHashingService::new(), - reveaal_service_mock: MockReveaalService::new(), - } - } - - pub fn disguise_context_mocks(mock_services: MockContexts) -> ContextCollection { - ContextCollection { - access_context: Arc::new(mock_services.access_context_mock), - in_use_context: Arc::new(mock_services.in_use_context_mock), - project_context: Arc::new(mock_services.project_context_mock), - query_context: Arc::new(mock_services.query_context_mock), - session_context: Arc::new(mock_services.session_context_mock), - user_context: Arc::new(mock_services.user_context_mock), - } - } - - pub fn disguise_service_mocks(mock_services: MockServices) -> ServiceCollection { - ServiceCollection { - hashing_service: Arc::new(mock_services.hashing_service_mock), - reveaal_service: Arc::new(mock_services.reveaal_service_mock), - } - } - - pub struct MockContexts { - pub(crate) access_context_mock: MockAccessContext, - pub(crate) in_use_context_mock: MockInUseContext, - pub(crate) project_context_mock: MockProjectContext, - pub(crate) query_context_mock: MockQueryContext, - pub(crate) session_context_mock: MockSessionContext, - pub(crate) user_context_mock: MockUserContext, - } - - pub struct MockServices { - pub(crate) hashing_service_mock: MockHashingService, - pub(crate) reveaal_service_mock: MockReveaalService, - } - - mock! { - pub AccessContext {} - #[async_trait] - impl EntityContextTrait for AccessContext { - async fn create(&self, entity: access::Model) -> Result; - async fn get_by_id(&self, entity_id: i32) -> Result, DbErr>; - async fn get_all(&self) -> Result, DbErr>; - async fn update(&self, entity: access::Model) -> Result; - async fn delete(&self, entity_id: i32) -> Result; - } - #[async_trait] - impl AccessContextTrait for AccessContext { - async fn get_access_by_uid_and_project_id( - &self, - uid: i32, - project_id: i32, - ) -> Result, DbErr>; - - async fn get_access_by_project_id( - &self, - project_id: i32, - ) -> Result, DbErr>; - } - } - - mock! { - pub InUseContext {} - #[async_trait] - impl EntityContextTrait for InUseContext { - async fn create(&self, entity: in_use::Model) -> Result; - async fn get_by_id(&self, entity_id: i32) -> Result, DbErr>; - async fn get_all(&self) -> Result, DbErr>; - async fn update(&self, entity: in_use::Model) -> Result; - async fn delete(&self, entity_id: i32) -> Result; - } - #[async_trait] - impl InUseContextTrait for InUseContext {} - } - - mock! { - pub ProjectContext {} - #[async_trait] - impl EntityContextTrait for ProjectContext { - async fn create(&self, entity: project::Model) -> Result; - async fn get_by_id(&self, entity_id: i32) -> Result, DbErr>; - async fn get_all(&self) -> Result, DbErr>; - async fn update(&self, entity: project::Model) -> Result; - async fn delete(&self, entity_id: i32) -> Result; - } - #[async_trait] - impl ProjectContextTrait for ProjectContext { - async fn get_project_info_by_uid(&self, uid: i32) -> Result, DbErr>; - } - } - - mock! { - pub QueryContext {} - #[async_trait] - impl EntityContextTrait for QueryContext { - async fn create(&self, entity: query::Model) -> Result; - async fn get_by_id(&self, entity_id: i32) -> Result, DbErr>; - async fn get_all(&self) -> Result, DbErr>; - async fn update(&self, entity: query::Model) -> Result; - async fn delete(&self, entity_id: i32) -> Result; - } - #[async_trait] - impl QueryContextTrait for QueryContext { - async fn get_all_by_project_id(&self, project_id: i32) -> Result, DbErr>; - } - } - - mock! { - pub SessionContext {} - #[async_trait] - impl EntityContextTrait for SessionContext { - async fn create(&self, entity: session::Model) -> Result; - async fn get_by_id(&self, entity_id: i32) -> Result, DbErr>; - async fn get_all(&self) -> Result, DbErr>; - async fn update(&self, entity: session::Model) -> Result; - async fn delete(&self, entity_id: i32) -> Result; - } - #[async_trait] - impl SessionContextTrait for SessionContext { - async fn get_by_token(&self, token_type: TokenType, token: String) -> Result, DbErr>; - async fn delete_by_token(&self, token_type: TokenType, token: String) -> Result; - } - } - - mock! { - pub UserContext {} - #[async_trait] - impl EntityContextTrait for UserContext { - async fn create(&self, entity: user::Model) -> Result; - async fn get_by_id(&self, entity_id: i32) -> Result, DbErr>; - async fn get_all(&self) -> Result, DbErr>; - async fn update(&self, entity: user::Model) -> Result; - async fn delete(&self, entity_id: i32) -> Result; - } - #[async_trait] - impl UserContextTrait for UserContext { - async fn get_by_username(&self, username: String) -> Result, DbErr>; - async fn get_by_email(&self, email: String) -> Result, DbErr>; - async fn get_by_ids(&self, ids: Vec) -> Result, DbErr>; - } - } - - mock! { - pub ReveaalService{} - #[async_trait] - impl ReveaalServiceTrait for ReveaalService { - async fn get_user_token(&self,request: Request<()>) -> Result, Status>; - async fn send_query(&self,request: Request) -> Result, Status>; - async fn start_simulation(&self, request: Request) -> Result, Status>; - async fn take_simulation_step(&self, request: Request) -> Result, Status>; - } - } - - mock! { - pub HashingService {} - impl HashingServiceTrait for HashingService { - fn hash_password(&self, password: String) -> Result; - fn verify_password(&self, password: String, hash: &str) -> Result; - } - } -} diff --git a/src/controllers/controller_traits/access_controller_trait.rs b/src/controllers/controller_traits/access_controller_trait.rs deleted file mode 100644 index babc3e6..0000000 --- a/src/controllers/controller_traits/access_controller_trait.rs +++ /dev/null @@ -1,45 +0,0 @@ -use crate::api::server::protobuf::{ - CreateAccessRequest, DeleteAccessRequest, ListAccessInfoRequest, ListAccessInfoResponse, - UpdateAccessRequest, -}; -use async_trait::async_trait; -use tonic::{Request, Response, Status}; - -#[async_trait] -pub trait AccessControllerTrait: Send + Sync { - /// handles the list_access_info endpoint - /// # Errors - /// If an invalid or non-existent [`ListAccessInfoRequest::project_id`] is provided - async fn list_access_info( - &self, - request: Request, - ) -> Result, Status>; - /// Creates an access in the contexts. - /// # Errors - /// Returns an error if the contexts context fails to create the access - async fn create_access( - &self, - request: Request, - ) -> Result, Status>; - - /// Endpoint for updating an access record. - /// - /// Takes [`UpdateAccessRequest`] as input - /// - /// Returns a [`Status`] as response - /// - /// `project_id` and `user_id` is set to 'default' since they won't be updated in the contexts. - async fn update_access( - &self, - request: Request, - ) -> Result, Status>; - - /// Deletes the an Access from the contexts. This has no sideeffects. - /// - /// # Errors - /// This function will return an error if the access does not exist in the contexts. - async fn delete_access( - &self, - request: Request, - ) -> Result, Status>; -} diff --git a/src/controllers/controller_traits/mod.rs b/src/controllers/controller_traits/mod.rs deleted file mode 100644 index 202ed99..0000000 --- a/src/controllers/controller_traits/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -mod access_controller_trait; -mod project_controller_trait; -mod query_controller_trait; -mod session_controller_trait; -mod user_controller_trait; - -pub use access_controller_trait::AccessControllerTrait; -pub use project_controller_trait::ProjectControllerTrait; -pub use query_controller_trait::QueryControllerTrait; -pub use session_controller_trait::SessionControllerTrait; -pub use user_controller_trait::UserControllerTrait; diff --git a/src/controllers/controller_traits/project_controller_trait.rs b/src/controllers/controller_traits/project_controller_trait.rs deleted file mode 100644 index 0aac153..0000000 --- a/src/controllers/controller_traits/project_controller_trait.rs +++ /dev/null @@ -1,51 +0,0 @@ -use crate::api::server::protobuf::{ - CreateProjectRequest, CreateProjectResponse, DeleteProjectRequest, GetProjectRequest, - GetProjectResponse, ListProjectsInfoResponse, UpdateProjectRequest, -}; -use async_trait::async_trait; -use tonic::{Request, Response, Status}; - -#[async_trait] -pub trait ProjectControllerTrait: Send + Sync { - /// Gets a project and its queries from the contexts. - /// - /// If the project is not in use, it will now be in use by the requester's session, - /// given that they are an Editor. - async fn get_project( - &self, - request: Request, - ) -> Result, Status>; - - /// Creates a project from [`CreateProjectRequest`] - /// # Errors - /// Errors on invalid JSON, invalid user id or if a project already exists - async fn create_project( - &self, - request: Request, - ) -> Result, Status>; - - /// Updates a Model in the contexts given its id. - /// - /// # Errors - /// This function will return an error if the project does not exist in the contexts - /// or if the user does not have access to the project with role 'Editor'. - async fn update_project( - &self, - request: Request, - ) -> Result, Status>; - - /// Deletes a Model from the contexts. - /// - /// # Errors - /// This function will return an error if the project does not exist in the contexts - /// or if the user is not the project owner. - async fn delete_project( - &self, - request: Request, - ) -> Result, Status>; - - async fn list_projects_info( - &self, - request: Request<()>, - ) -> Result, Status>; -} diff --git a/src/controllers/controller_traits/query_controller_trait.rs b/src/controllers/controller_traits/query_controller_trait.rs deleted file mode 100644 index 5d232ca..0000000 --- a/src/controllers/controller_traits/query_controller_trait.rs +++ /dev/null @@ -1,41 +0,0 @@ -use crate::api::server::protobuf::{ - CreateQueryRequest, DeleteQueryRequest, SendQueryRequest, SendQueryResponse, UpdateQueryRequest, -}; -use async_trait::async_trait; -use tonic::{Request, Response, Status}; - -#[async_trait] -pub trait QueryControllerTrait: Send + Sync { - /// Creates a query in the contexts - /// # Errors - /// Returns an error if the contexts context fails to create the query or - async fn create_query( - &self, - request: Request, - ) -> Result, Status>; - - /// Endpoint for updating a query record. - /// # Errors - /// Errors on non existent entity, parsing error or invalid rights - async fn update_query( - &self, - request: Request, - ) -> Result, Status>; - - /// Deletes a query record in the contexts. - /// # Errors - /// Returns an error if the provided query_id is not found in the contexts. - async fn delete_query( - &self, - request: Request, - ) -> Result, Status>; - - /// Sends a query to be run on Reveaal. - /// After query is run the result is stored in the contexts. - /// - /// Returns the response that is received from Reveaal. - async fn send_query( - &self, - request: Request, - ) -> Result, Status>; -} diff --git a/src/controllers/controller_traits/session_controller_trait.rs b/src/controllers/controller_traits/session_controller_trait.rs deleted file mode 100644 index 312cddf..0000000 --- a/src/controllers/controller_traits/session_controller_trait.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::api::server::protobuf::{GetAuthTokenRequest, GetAuthTokenResponse}; -use async_trait::async_trait; -use tonic::{Request, Response, Status}; - -#[async_trait] -pub trait SessionControllerTrait: Send + Sync { - /// Deletes the requester's session, found by their access token. - /// - /// Returns the response that is received from Reveaal. - async fn delete_session(&self, _request: Request<()>) -> Result, Status>; - - /// This method is used to get a new access and refresh token for a user. - /// - /// # Errors - /// This function will return an error if the user does not exist in the contexts, - /// if the password in the request does not match the user's password, - /// or if no user is provided in the request. - async fn get_auth_token( - &self, - request: Request, - ) -> Result, Status>; -} diff --git a/src/controllers/controller_traits/user_controller_trait.rs b/src/controllers/controller_traits/user_controller_trait.rs deleted file mode 100644 index 28ed8f6..0000000 --- a/src/controllers/controller_traits/user_controller_trait.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::api::server::protobuf::{ - CreateUserRequest, GetUsersRequest, GetUsersResponse, UpdateUserRequest, -}; -use async_trait::async_trait; -use tonic::{Request, Response, Status}; - -#[async_trait] -pub trait UserControllerTrait: Send + Sync { - async fn create_user( - &self, - request: Request, - ) -> Result, Status>; - async fn update_user( - &self, - request: Request, - ) -> Result, Status>; - async fn delete_user(&self, request: Request<()>) -> Result, Status>; - async fn get_users( - &self, - request: Request, - ) -> Result, Status>; -} diff --git a/src/controllers/mod.rs b/src/controllers/mod.rs index ad896bd..100b38a 100644 --- a/src/controllers/mod.rs +++ b/src/controllers/mod.rs @@ -1,3 +1,207 @@ -pub mod controller_collection; -pub mod controller_impls; -pub mod controller_traits; +mod access_controller; +mod controller_collection; +mod project_controller; +mod query_controller; +mod reveaal_controller; +mod session_controller; +mod user_controller; + +pub use access_controller::*; +pub use controller_collection::*; +pub use project_controller::*; +pub use query_controller::*; +pub use reveaal_controller::*; +pub use session_controller::*; +pub use user_controller::*; + +#[cfg(test)] +mod helpers { + use crate::api::auth::TokenType; + use crate::api::server::protobuf::{ + AccessInfo, ProjectInfo, QueryRequest, QueryResponse, SimulationStartRequest, + SimulationStepRequest, SimulationStepResponse, UserTokenResponse, + }; + use crate::contexts::*; + use crate::entities::{access, in_use, project, query, session, user}; + use crate::services::*; + use async_trait::async_trait; + use mockall::mock; + use sea_orm::DbErr; + use std::sync::Arc; + use tonic::{Request, Response, Status}; + + pub fn get_mock_contexts() -> MockContexts { + MockContexts { + access_context_mock: MockAccessContext::new(), + in_use_context_mock: MockInUseContext::new(), + project_context_mock: MockProjectContext::new(), + query_context_mock: MockQueryContext::new(), + session_context_mock: MockSessionContext::new(), + user_context_mock: MockUserContext::new(), + } + } + + pub fn get_mock_services() -> MockServices { + MockServices { + hashing_service_mock: MockHashingService::new(), + reveaal_service_mock: MockReveaalService::new(), + } + } + + pub fn disguise_context_mocks(mock_services: MockContexts) -> ContextCollection { + ContextCollection { + access_context: Arc::new(mock_services.access_context_mock), + in_use_context: Arc::new(mock_services.in_use_context_mock), + project_context: Arc::new(mock_services.project_context_mock), + query_context: Arc::new(mock_services.query_context_mock), + session_context: Arc::new(mock_services.session_context_mock), + user_context: Arc::new(mock_services.user_context_mock), + } + } + + pub fn disguise_service_mocks(mock_services: MockServices) -> ServiceCollection { + ServiceCollection { + hashing_service: Arc::new(mock_services.hashing_service_mock), + reveaal_service: Arc::new(mock_services.reveaal_service_mock), + } + } + + pub struct MockContexts { + pub(crate) access_context_mock: MockAccessContext, + pub(crate) in_use_context_mock: MockInUseContext, + pub(crate) project_context_mock: MockProjectContext, + pub(crate) query_context_mock: MockQueryContext, + pub(crate) session_context_mock: MockSessionContext, + pub(crate) user_context_mock: MockUserContext, + } + + pub struct MockServices { + pub(crate) hashing_service_mock: MockHashingService, + pub(crate) reveaal_service_mock: MockReveaalService, + } + + mock! { + pub AccessContext {} + #[async_trait] + impl EntityContextTrait for AccessContext { + async fn create(&self, entity: access::Model) -> Result; + async fn get_by_id(&self, entity_id: i32) -> Result, DbErr>; + async fn get_all(&self) -> Result, DbErr>; + async fn update(&self, entity: access::Model) -> Result; + async fn delete(&self, entity_id: i32) -> Result; + } + #[async_trait] + impl AccessContextTrait for AccessContext { + async fn get_access_by_uid_and_project_id( + &self, + uid: i32, + project_id: i32, + ) -> Result, DbErr>; + + async fn get_access_by_project_id( + &self, + project_id: i32, + ) -> Result, DbErr>; + } + } + + mock! { + pub InUseContext {} + #[async_trait] + impl EntityContextTrait for InUseContext { + async fn create(&self, entity: in_use::Model) -> Result; + async fn get_by_id(&self, entity_id: i32) -> Result, DbErr>; + async fn get_all(&self) -> Result, DbErr>; + async fn update(&self, entity: in_use::Model) -> Result; + async fn delete(&self, entity_id: i32) -> Result; + } + #[async_trait] + impl InUseContextTrait for InUseContext {} + } + + mock! { + pub ProjectContext {} + #[async_trait] + impl EntityContextTrait for ProjectContext { + async fn create(&self, entity: project::Model) -> Result; + async fn get_by_id(&self, entity_id: i32) -> Result, DbErr>; + async fn get_all(&self) -> Result, DbErr>; + async fn update(&self, entity: project::Model) -> Result; + async fn delete(&self, entity_id: i32) -> Result; + } + #[async_trait] + impl ProjectContextTrait for ProjectContext { + async fn get_project_info_by_uid(&self, uid: i32) -> Result, DbErr>; + } + } + + mock! { + pub QueryContext {} + #[async_trait] + impl EntityContextTrait for QueryContext { + async fn create(&self, entity: query::Model) -> Result; + async fn get_by_id(&self, entity_id: i32) -> Result, DbErr>; + async fn get_all(&self) -> Result, DbErr>; + async fn update(&self, entity: query::Model) -> Result; + async fn delete(&self, entity_id: i32) -> Result; + } + #[async_trait] + impl QueryContextTrait for QueryContext { + async fn get_all_by_project_id(&self, project_id: i32) -> Result, DbErr>; + } + } + + mock! { + pub SessionContext {} + #[async_trait] + impl EntityContextTrait for SessionContext { + async fn create(&self, entity: session::Model) -> Result; + async fn get_by_id(&self, entity_id: i32) -> Result, DbErr>; + async fn get_all(&self) -> Result, DbErr>; + async fn update(&self, entity: session::Model) -> Result; + async fn delete(&self, entity_id: i32) -> Result; + } + #[async_trait] + impl SessionContextTrait for SessionContext { + async fn get_by_token(&self, token_type: TokenType, token: String) -> Result, DbErr>; + async fn delete_by_token(&self, token_type: TokenType, token: String) -> Result; + } + } + + mock! { + pub UserContext {} + #[async_trait] + impl EntityContextTrait for UserContext { + async fn create(&self, entity: user::Model) -> Result; + async fn get_by_id(&self, entity_id: i32) -> Result, DbErr>; + async fn get_all(&self) -> Result, DbErr>; + async fn update(&self, entity: user::Model) -> Result; + async fn delete(&self, entity_id: i32) -> Result; + } + #[async_trait] + impl UserContextTrait for UserContext { + async fn get_by_username(&self, username: String) -> Result, DbErr>; + async fn get_by_email(&self, email: String) -> Result, DbErr>; + async fn get_by_ids(&self, ids: Vec) -> Result, DbErr>; + } + } + + mock! { + pub ReveaalService{} + #[async_trait] + impl ReveaalServiceTrait for ReveaalService { + async fn get_user_token(&self,request: Request<()>) -> Result, Status>; + async fn send_query(&self,request: Request) -> Result, Status>; + async fn start_simulation(&self, request: Request) -> Result, Status>; + async fn take_simulation_step(&self, request: Request) -> Result, Status>; + } + } + + mock! { + pub HashingService {} + impl HashingServiceTrait for HashingService { + fn hash_password(&self, password: String) -> Result; + fn verify_password(&self, password: String, hash: &str) -> Result; + } + } +} diff --git a/src/controllers/controller_impls/project_controller.rs b/src/controllers/project_controller.rs similarity index 97% rename from src/controllers/controller_impls/project_controller.rs rename to src/controllers/project_controller.rs index 2c14ca5..f75fea5 100644 --- a/src/controllers/controller_impls/project_controller.rs +++ b/src/controllers/project_controller.rs @@ -3,8 +3,7 @@ use crate::api::server::protobuf::{ CreateProjectRequest, CreateProjectResponse, DeleteProjectRequest, GetProjectRequest, GetProjectResponse, ListProjectsInfoResponse, Project, Query, UpdateProjectRequest, }; -use crate::contexts::context_collection::ContextCollection; -use crate::controllers::controller_traits::ProjectControllerTrait; +use crate::contexts::ContextCollection; use crate::entities::{access, in_use, project}; use async_trait::async_trait; use chrono::{Duration, Utc}; @@ -13,6 +12,51 @@ use tonic::{Code, Request, Response, Status}; const IN_USE_DURATION_MINUTES: i64 = 10; +#[async_trait] +pub trait ProjectControllerTrait: Send + Sync { + /// Gets a project and its queries from the contexts. + /// + /// If the project is not in use, it will now be in use by the requester's session, + /// given that they are an Editor. + async fn get_project( + &self, + request: Request, + ) -> Result, Status>; + + /// Creates a project from [`CreateProjectRequest`] + /// # Errors + /// Errors on invalid JSON, invalid user id or if a project already exists + async fn create_project( + &self, + request: Request, + ) -> Result, Status>; + + /// Updates a Model in the contexts given its id. + /// + /// # Errors + /// This function will return an error if the project does not exist in the contexts + /// or if the user does not have access to the project with role 'Editor'. + async fn update_project( + &self, + request: Request, + ) -> Result, Status>; + + /// Deletes a Model from the contexts. + /// + /// # Errors + /// This function will return an error if the project does not exist in the contexts + /// or if the user is not the project owner. + async fn delete_project( + &self, + request: Request, + ) -> Result, Status>; + + async fn list_projects_info( + &self, + request: Request<()>, + ) -> Result, Status>; +} + pub struct ProjectController { contexts: ContextCollection, } @@ -493,8 +537,8 @@ impl ProjectControllerTrait for ProjectController { mod tests { use super::super::helpers::disguise_context_mocks; use super::super::helpers::get_mock_contexts; - use crate::controllers::controller_impls::ProjectController; - use crate::controllers::controller_traits::ProjectControllerTrait; + use crate::controllers::ProjectController; + use crate::controllers::ProjectControllerTrait; use crate::{ api::{ auth::TokenType, diff --git a/src/controllers/controller_impls/query_controller.rs b/src/controllers/query_controller.rs similarity index 95% rename from src/controllers/controller_impls/query_controller.rs rename to src/controllers/query_controller.rs index a1b96d8..5164d4b 100644 --- a/src/controllers/controller_impls/query_controller.rs +++ b/src/controllers/query_controller.rs @@ -3,13 +3,48 @@ use crate::api::server::protobuf::{ CreateQueryRequest, DeleteQueryRequest, QueryRequest, SendQueryRequest, SendQueryResponse, UpdateQueryRequest, }; -use crate::contexts::context_collection::ContextCollection; -use crate::controllers::controller_traits::QueryControllerTrait; +use crate::contexts::ContextCollection; use crate::entities::query; -use crate::services::service_collection::ServiceCollection; +use crate::services::ServiceCollection; use async_trait::async_trait; use tonic::{Code, Request, Response, Status}; +#[async_trait] +pub trait QueryControllerTrait: Send + Sync { + /// Creates a query in the contexts + /// # Errors + /// Returns an error if the contexts context fails to create the query or + async fn create_query( + &self, + request: Request, + ) -> Result, Status>; + + /// Endpoint for updating a query record. + /// # Errors + /// Errors on non existent entity, parsing error or invalid rights + async fn update_query( + &self, + request: Request, + ) -> Result, Status>; + + /// Deletes a query record in the contexts. + /// # Errors + /// Returns an error if the provided query_id is not found in the contexts. + async fn delete_query( + &self, + request: Request, + ) -> Result, Status>; + + /// Sends a query to be run on Reveaal. + /// After query is run the result is stored in the contexts. + /// + /// Returns the response that is received from Reveaal. + async fn send_query( + &self, + request: Request, + ) -> Result, Status>; +} + pub struct QueryController { contexts: ContextCollection, services: ServiceCollection, @@ -311,8 +346,8 @@ mod tests { use crate::api::server::protobuf::{ CreateQueryRequest, DeleteQueryRequest, QueryResponse, SendQueryRequest, UpdateQueryRequest, }; - use crate::controllers::controller_impls::QueryController; - use crate::controllers::controller_traits::QueryControllerTrait; + use crate::controllers::QueryController; + use crate::controllers::QueryControllerTrait; use crate::entities::{access, project, query}; use mockall::predicate; use sea_orm::DbErr; diff --git a/src/controllers/controller_impls/reveaal_controller.rs b/src/controllers/reveaal_controller.rs similarity index 96% rename from src/controllers/controller_impls/reveaal_controller.rs rename to src/controllers/reveaal_controller.rs index d7fcfe5..ba225e7 100644 --- a/src/controllers/controller_impls/reveaal_controller.rs +++ b/src/controllers/reveaal_controller.rs @@ -3,7 +3,7 @@ use crate::api::server::protobuf::{ QueryRequest, QueryResponse, SimulationStartRequest, SimulationStepRequest, SimulationStepResponse, UserTokenResponse, }; -use crate::services::service_collection::ServiceCollection; +use crate::services::ServiceCollection; use async_trait::async_trait; use tonic::{Request, Response, Status}; diff --git a/src/controllers/controller_impls/session_controller.rs b/src/controllers/session_controller.rs similarity index 95% rename from src/controllers/controller_impls/session_controller.rs rename to src/controllers/session_controller.rs index 8f8027c..f987eb4 100644 --- a/src/controllers/controller_impls/session_controller.rs +++ b/src/controllers/session_controller.rs @@ -1,14 +1,32 @@ use crate::api::auth::{RequestExt, Token, TokenError, TokenType}; use crate::api::server::protobuf::get_auth_token_request::{user_credentials, UserCredentials}; use crate::api::server::protobuf::{GetAuthTokenRequest, GetAuthTokenResponse}; -use crate::contexts::context_collection::ContextCollection; -use crate::controllers::controller_traits::SessionControllerTrait; +use crate::contexts::ContextCollection; use crate::entities::{session, user}; -use crate::services::service_collection::ServiceCollection; +use crate::services::ServiceCollection; use async_trait::async_trait; use sea_orm::DbErr; use tonic::{Code, Request, Response, Status}; +#[async_trait] +pub trait SessionControllerTrait: Send + Sync { + /// Deletes the requester's session, found by their access token. + /// + /// Returns the response that is received from Reveaal. + async fn delete_session(&self, _request: Request<()>) -> Result, Status>; + + /// This method is used to get a new access and refresh token for a user. + /// + /// # Errors + /// This function will return an error if the user does not exist in the contexts, + /// if the password in the request does not match the user's password, + /// or if no user is provided in the request. + async fn get_auth_token( + &self, + request: Request, + ) -> Result, Status>; +} + pub struct SessionController { contexts: ContextCollection, services: ServiceCollection, @@ -206,8 +224,8 @@ mod tests { use crate::api::auth::{Token, TokenType}; use crate::api::server::protobuf::get_auth_token_request::{user_credentials, UserCredentials}; use crate::api::server::protobuf::GetAuthTokenRequest; - use crate::controllers::controller_impls::SessionController; - use crate::controllers::controller_traits::SessionControllerTrait; + use crate::controllers::SessionController; + use crate::controllers::SessionControllerTrait; use sea_orm::DbErr; use tonic::{metadata, Code, Request}; diff --git a/src/controllers/controller_impls/user_controller.rs b/src/controllers/user_controller.rs similarity index 96% rename from src/controllers/controller_impls/user_controller.rs rename to src/controllers/user_controller.rs index e8c2890..142ca4c 100644 --- a/src/controllers/controller_impls/user_controller.rs +++ b/src/controllers/user_controller.rs @@ -3,15 +3,31 @@ use crate::api::server::protobuf::get_users_response::UserInfo; use crate::api::server::protobuf::{ CreateUserRequest, GetUsersRequest, GetUsersResponse, UpdateUserRequest, }; -use crate::contexts::context_collection::ContextCollection; -use crate::controllers::controller_traits::UserControllerTrait; +use crate::contexts::ContextCollection; use crate::entities::user; -use crate::services::service_collection::ServiceCollection; +use crate::services::ServiceCollection; use async_trait::async_trait; use regex::Regex; use sea_orm::SqlErr; use tonic::{Code, Request, Response, Status}; +#[async_trait] +pub trait UserControllerTrait: Send + Sync { + async fn create_user( + &self, + request: Request, + ) -> Result, Status>; + async fn update_user( + &self, + request: Request, + ) -> Result, Status>; + async fn delete_user(&self, request: Request<()>) -> Result, Status>; + async fn get_users( + &self, + request: Request, + ) -> Result, Status>; +} + pub struct UserController { contexts: ContextCollection, services: ServiceCollection, @@ -208,8 +224,8 @@ mod tests { disguise_context_mocks, disguise_service_mocks, get_mock_contexts, get_mock_services, }; use crate::api::server::protobuf::{CreateUserRequest, GetUsersRequest, UpdateUserRequest}; - use crate::controllers::controller_impls::UserController; - use crate::controllers::controller_traits::UserControllerTrait; + use crate::controllers::UserController; + use crate::controllers::UserControllerTrait; use crate::entities::user; use mockall::predicate; use sea_orm::DbErr; diff --git a/src/main.rs b/src/main.rs index 69b6721..9c4e26b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,13 +12,9 @@ mod controllers; mod entities; mod services; -use crate::contexts::context_collection::ContextCollection; -use crate::contexts::context_impls::*; -use crate::contexts::context_traits::DatabaseContextTrait; -use crate::controllers::controller_collection::ControllerCollection; -use crate::controllers::controller_impls::*; -use crate::services::service_collection::ServiceCollection; -use crate::services::service_impls::{HashingService, ReveaalService}; +use crate::contexts::*; +use crate::controllers::*; +use crate::services::{HashingService, ReveaalService, ServiceCollection}; use api::server::start_grpc_server; use dotenv::dotenv; use sea_orm::{ConnectionTrait, Database, DbBackend}; @@ -27,7 +23,6 @@ use std::error::Error; use std::sync::Arc; #[tokio::main] -#[allow(clippy::expect_used)] async fn main() -> Result<(), Box> { dotenv().ok(); diff --git a/src/services/service_impls/hashing_service.rs b/src/services/hashing_service.rs similarity index 63% rename from src/services/service_impls/hashing_service.rs rename to src/services/hashing_service.rs index 112eb84..2b679e5 100644 --- a/src/services/service_impls/hashing_service.rs +++ b/src/services/hashing_service.rs @@ -1,6 +1,10 @@ -use crate::services::service_traits::hashing_service_trait::HashingServiceTrait; use bcrypt::{hash, verify, BcryptError, DEFAULT_COST}; +pub trait HashingServiceTrait: Send + Sync { + fn hash_password(&self, password: String) -> Result; + fn verify_password(&self, password: String, hash: &str) -> Result; +} + pub struct HashingService; impl HashingServiceTrait for HashingService { diff --git a/src/services/mod.rs b/src/services/mod.rs index 22340aa..0c9455b 100644 --- a/src/services/mod.rs +++ b/src/services/mod.rs @@ -1,3 +1,7 @@ -pub mod service_collection; -pub mod service_impls; -pub mod service_traits; +mod hashing_service; +mod reveaal_service; +mod service_collection; + +pub use hashing_service::*; +pub use reveaal_service::*; +pub use service_collection::*; diff --git a/src/services/service_impls/reveaal_service.rs b/src/services/reveaal_service.rs similarity index 81% rename from src/services/service_impls/reveaal_service.rs rename to src/services/reveaal_service.rs index 6c757f5..221e583 100644 --- a/src/services/service_impls/reveaal_service.rs +++ b/src/services/reveaal_service.rs @@ -3,11 +3,30 @@ use crate::api::server::protobuf::{ QueryRequest, QueryResponse, SimulationStartRequest, SimulationStepRequest, SimulationStepResponse, UserTokenResponse, }; -use crate::services::service_traits::ReveaalServiceTrait; use async_trait::async_trait; use tonic::transport::Channel; use tonic::{Request, Response, Status}; +#[async_trait] +pub trait ReveaalServiceTrait: Send + Sync { + async fn get_user_token( + &self, + request: Request<()>, + ) -> Result, Status>; + async fn send_query( + &self, + request: Request, + ) -> Result, Status>; + async fn start_simulation( + &self, + request: Request, + ) -> Result, Status>; + async fn take_simulation_step( + &self, + request: Request, + ) -> Result, Status>; +} + pub struct ReveaalService { address: String, } diff --git a/src/services/service_collection.rs b/src/services/service_collection.rs index b487c85..2d7d109 100644 --- a/src/services/service_collection.rs +++ b/src/services/service_collection.rs @@ -1,4 +1,4 @@ -use crate::services::service_traits::{HashingServiceTrait, ReveaalServiceTrait}; +use crate::services::{HashingServiceTrait, ReveaalServiceTrait}; use std::sync::Arc; #[derive(Clone)] diff --git a/src/services/service_impls/mod.rs b/src/services/service_impls/mod.rs deleted file mode 100644 index 3b93ad7..0000000 --- a/src/services/service_impls/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod hashing_service; -mod reveaal_service; - -pub use hashing_service::HashingService; -pub use reveaal_service::ReveaalService; diff --git a/src/services/service_traits/hashing_service_trait.rs b/src/services/service_traits/hashing_service_trait.rs deleted file mode 100644 index 5b36e95..0000000 --- a/src/services/service_traits/hashing_service_trait.rs +++ /dev/null @@ -1,6 +0,0 @@ -use bcrypt::BcryptError; - -pub trait HashingServiceTrait: Send + Sync { - fn hash_password(&self, password: String) -> Result; - fn verify_password(&self, password: String, hash: &str) -> Result; -} diff --git a/src/services/service_traits/mod.rs b/src/services/service_traits/mod.rs deleted file mode 100644 index 0761798..0000000 --- a/src/services/service_traits/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod hashing_service_trait; -pub mod reveaal_service_trait; - -pub use hashing_service_trait::HashingServiceTrait; -pub use reveaal_service_trait::ReveaalServiceTrait; diff --git a/src/services/service_traits/reveaal_service_trait.rs b/src/services/service_traits/reveaal_service_trait.rs deleted file mode 100644 index 66fcfde..0000000 --- a/src/services/service_traits/reveaal_service_trait.rs +++ /dev/null @@ -1,26 +0,0 @@ -use crate::api::server::protobuf::{ - QueryRequest, QueryResponse, SimulationStartRequest, SimulationStepRequest, - SimulationStepResponse, UserTokenResponse, -}; -use async_trait::async_trait; -use tonic::{Request, Response, Status}; - -#[async_trait] -pub trait ReveaalServiceTrait: Send + Sync { - async fn get_user_token( - &self, - request: Request<()>, - ) -> Result, Status>; - async fn send_query( - &self, - request: Request, - ) -> Result, Status>; - async fn start_simulation( - &self, - request: Request, - ) -> Result, Status>; - async fn take_simulation_step( - &self, - request: Request, - ) -> Result, Status>; -} From 3bc064e00bccf9712cc9d9dc98ab864d63b9a45e Mon Sep 17 00:00:00 2001 From: Thomas Lohse Date: Mon, 26 Feb 2024 11:47:09 +0100 Subject: [PATCH 3/6] formatting --- Cargo.toml | 6 +++--- src/contexts/access_context.rs | 7 +++---- .../db_centexts/postgres_database_context.rs | 2 +- .../db_centexts/sqlite_database_context.rs | 2 +- src/contexts/in_use_context.rs | 2 +- src/contexts/project_context.rs | 3 +-- src/contexts/query_context.rs | 3 +-- src/contexts/session_context.rs | 3 +-- src/contexts/user_context.rs | 3 +-- src/controllers/access_controller.rs | 4 +--- src/controllers/project_controller.rs | 2 +- src/controllers/reveaal_controller.rs | 5 ++--- src/controllers/session_controller.rs | 6 ++++-- src/controllers/user_controller.rs | 4 ++-- src/lib.rs | 4 ++-- src/main.rs | 14 +++++++++----- src/services/reveaal_service.rs | 8 +++----- 17 files changed, 37 insertions(+), 41 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 015447e..5a0e77d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ dotenv = "0.15.0" sea-orm = { version = "^0.12.0", features = ["sqlx-postgres", "runtime-async-std-native-tls", "macros", "tests-cfg", "sqlx-sqlite"] } async-trait = { version = "0.1.73", features = [] } futures = "0.3.28" -tonic = "0.10.2" +tonic = "0.11.0" prost = "0.12.1" log = "0.4.20" jsonwebtoken = "9.1.0" @@ -40,7 +40,7 @@ serde = "1.0.189" chrono = "0.4.31" uuid = { version = "1.5.0", features = ["v4"] } regex = "1.10.2" -mockall = "0.11.4" +mockall = "0.12.1" bcrypt = "0.15.0" serde_json = "1.0.108" syn = { version = "2.0", features = ["full"] } @@ -50,7 +50,7 @@ migration = { path = "migration" } thiserror = "1.0.50" [build-dependencies] -tonic-build = "0.10.2" +tonic-build = "0.11.0" [lints.clippy] complexity = "deny" diff --git a/src/contexts/access_context.rs b/src/contexts/access_context.rs index f2d74e8..2687363 100644 --- a/src/contexts/access_context.rs +++ b/src/contexts/access_context.rs @@ -1,6 +1,5 @@ use crate::api::server::protobuf::AccessInfo; -use crate::contexts::db_centexts::DatabaseContextTrait; -use crate::contexts::EntityContextTrait; +use crate::contexts::{DatabaseContextTrait, EntityContextTrait}; use crate::entities::access; use sea_orm::prelude::async_trait::async_trait; use sea_orm::ActiveValue::{Set, Unchanged}; @@ -152,7 +151,7 @@ mod tests { create_accesses, create_projects, create_users, get_reset_database_context, }; use crate::api::server::protobuf::AccessInfo; - use crate::contexts::access_context::AccessContextTrait; + use crate::contexts::AccessContextTrait; use crate::contexts::EntityContextTrait; use crate::{ contexts::AccessContext, @@ -532,7 +531,7 @@ mod tests { let access = access_context.get_access_by_project_id(model.id).await; - assert!(access.unwrap() == expected_access_access_info_vector); + assert_eq!(access.unwrap(), expected_access_access_info_vector); } #[tokio::test] diff --git a/src/contexts/db_centexts/postgres_database_context.rs b/src/contexts/db_centexts/postgres_database_context.rs index 1e30eb5..954c552 100644 --- a/src/contexts/db_centexts/postgres_database_context.rs +++ b/src/contexts/db_centexts/postgres_database_context.rs @@ -1,4 +1,4 @@ -use crate::contexts::db_centexts::DatabaseContextTrait; +use crate::contexts::DatabaseContextTrait; use async_trait::async_trait; use migration::{Migrator, MigratorTrait}; use sea_orm::{ConnectionTrait, Database, DatabaseConnection, DbBackend, DbErr}; diff --git a/src/contexts/db_centexts/sqlite_database_context.rs b/src/contexts/db_centexts/sqlite_database_context.rs index a0e29ed..60fadfc 100644 --- a/src/contexts/db_centexts/sqlite_database_context.rs +++ b/src/contexts/db_centexts/sqlite_database_context.rs @@ -1,4 +1,4 @@ -use crate::contexts::db_centexts::DatabaseContextTrait; +use crate::contexts::DatabaseContextTrait; use migration::{Migrator, MigratorTrait}; use sea_orm::prelude::async_trait::async_trait; use sea_orm::{ConnectionTrait, Database, DatabaseConnection, DbBackend, DbErr}; diff --git a/src/contexts/in_use_context.rs b/src/contexts/in_use_context.rs index 49e0185..330361c 100644 --- a/src/contexts/in_use_context.rs +++ b/src/contexts/in_use_context.rs @@ -1,4 +1,4 @@ -use crate::contexts::{db_centexts::DatabaseContextTrait, EntityContextTrait}; +use crate::contexts::{DatabaseContextTrait, EntityContextTrait}; use crate::entities::in_use; use async_trait::async_trait; use chrono::Utc; diff --git a/src/contexts/project_context.rs b/src/contexts/project_context.rs index 71e4f09..8f4ab3d 100644 --- a/src/contexts/project_context.rs +++ b/src/contexts/project_context.rs @@ -1,8 +1,7 @@ -use crate::contexts::EntityContextTrait; use crate::entities::{access, project, query}; use crate::api::server::protobuf::ProjectInfo; -use crate::contexts::db_centexts::DatabaseContextTrait; +use crate::contexts::{DatabaseContextTrait, EntityContextTrait}; use async_trait::async_trait; use sea_orm::{ ActiveModelTrait, ColumnTrait, DbErr, EntityTrait, IntoActiveModel, JoinType, ModelTrait, diff --git a/src/contexts/query_context.rs b/src/contexts/query_context.rs index 49f0199..fe77d78 100644 --- a/src/contexts/query_context.rs +++ b/src/contexts/query_context.rs @@ -1,5 +1,4 @@ -use crate::contexts::db_centexts::DatabaseContextTrait; -use crate::contexts::EntityContextTrait; +use crate::contexts::{DatabaseContextTrait, EntityContextTrait}; use crate::entities::query; use sea_orm::prelude::async_trait::async_trait; use sea_orm::ActiveValue::{Set, Unchanged}; diff --git a/src/contexts/session_context.rs b/src/contexts/session_context.rs index d8b5892..77b37d5 100644 --- a/src/contexts/session_context.rs +++ b/src/contexts/session_context.rs @@ -1,6 +1,5 @@ use crate::api::auth::TokenType; -use crate::contexts::db_centexts::DatabaseContextTrait; -use crate::contexts::EntityContextTrait; +use crate::contexts::{DatabaseContextTrait, EntityContextTrait}; use crate::entities::session; use chrono::Local; use sea_orm::prelude::async_trait::async_trait; diff --git a/src/contexts/user_context.rs b/src/contexts/user_context.rs index 9b34bf6..cf390d8 100644 --- a/src/contexts/user_context.rs +++ b/src/contexts/user_context.rs @@ -1,5 +1,4 @@ -use crate::contexts::db_centexts::DatabaseContextTrait; -use crate::contexts::EntityContextTrait; +use crate::contexts::{DatabaseContextTrait, EntityContextTrait}; use crate::entities::user; use sea_orm::prelude::async_trait::async_trait; use sea_orm::ActiveValue::{Set, Unchanged}; diff --git a/src/controllers/access_controller.rs b/src/controllers/access_controller.rs index 3863c9f..c5fa96b 100644 --- a/src/controllers/access_controller.rs +++ b/src/controllers/access_controller.rs @@ -4,9 +4,7 @@ use crate::api::server::protobuf::{ CreateAccessRequest, DeleteAccessRequest, ListAccessInfoRequest, ListAccessInfoResponse, UpdateAccessRequest, }; -use crate::contexts::AccessContextTrait; -use crate::contexts::ContextCollection; -use crate::contexts::UserContextTrait; +use crate::contexts::{AccessContextTrait, ContextCollection, UserContextTrait}; use crate::entities::{access, user}; use async_trait::async_trait; use std::sync::Arc; diff --git a/src/controllers/project_controller.rs b/src/controllers/project_controller.rs index f75fea5..277f192 100644 --- a/src/controllers/project_controller.rs +++ b/src/controllers/project_controller.rs @@ -892,7 +892,7 @@ mod tests { let res = project_logic.get_project(request).await.unwrap_err(); - assert!(res.code() == Code::PermissionDenied); + assert_eq!(res.code(), Code::PermissionDenied); } #[tokio::test] diff --git a/src/controllers/reveaal_controller.rs b/src/controllers/reveaal_controller.rs index ba225e7..c6f0f3e 100644 --- a/src/controllers/reveaal_controller.rs +++ b/src/controllers/reveaal_controller.rs @@ -1,7 +1,6 @@ -use crate::api::server::protobuf::ecdar_backend_server::EcdarBackend; use crate::api::server::protobuf::{ - QueryRequest, QueryResponse, SimulationStartRequest, SimulationStepRequest, - SimulationStepResponse, UserTokenResponse, + ecdar_backend_server::EcdarBackend, QueryRequest, QueryResponse, SimulationStartRequest, + SimulationStepRequest, SimulationStepResponse, UserTokenResponse, }; use crate::services::ServiceCollection; use async_trait::async_trait; diff --git a/src/controllers/session_controller.rs b/src/controllers/session_controller.rs index f987eb4..3e9cf92 100644 --- a/src/controllers/session_controller.rs +++ b/src/controllers/session_controller.rs @@ -1,6 +1,8 @@ use crate::api::auth::{RequestExt, Token, TokenError, TokenType}; -use crate::api::server::protobuf::get_auth_token_request::{user_credentials, UserCredentials}; -use crate::api::server::protobuf::{GetAuthTokenRequest, GetAuthTokenResponse}; +use crate::api::server::protobuf::{ + get_auth_token_request::{user_credentials, UserCredentials}, + GetAuthTokenRequest, GetAuthTokenResponse, +}; use crate::contexts::ContextCollection; use crate::entities::{session, user}; use crate::services::ServiceCollection; diff --git a/src/controllers/user_controller.rs b/src/controllers/user_controller.rs index 142ca4c..f808d4e 100644 --- a/src/controllers/user_controller.rs +++ b/src/controllers/user_controller.rs @@ -1,7 +1,7 @@ use crate::api::auth::RequestExt; -use crate::api::server::protobuf::get_users_response::UserInfo; use crate::api::server::protobuf::{ - CreateUserRequest, GetUsersRequest, GetUsersResponse, UpdateUserRequest, + get_users_response::UserInfo, CreateUserRequest, GetUsersRequest, GetUsersResponse, + UpdateUserRequest, }; use crate::contexts::ContextCollection; use crate::entities::user; diff --git a/src/lib.rs b/src/lib.rs index 076f272..1862e56 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,7 +15,7 @@ pub fn endpoints(_attr: TokenStream, item: TokenStream) -> TokenStream { .as_ref() .map(|(_, items)| { items.iter().filter_map(|item| { - if let syn::Item::Impl(impl_item) = item { + if let Item::Impl(impl_item) = item { Some( impl_item .trait_ @@ -75,7 +75,7 @@ pub fn endpoints(_attr: TokenStream, item: TokenStream) -> TokenStream { .items .iter() .filter_map(|item| match item { - syn::ImplItem::Fn(function) => { + ImplItem::Fn(function) => { Some(function.sig.ident.to_string().to_case(Case::Pascal)) } _ => None, diff --git a/src/main.rs b/src/main.rs index 9c4e26b..a9fdbcb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,8 @@ //! # Description //! This project serves as an API server between an ECDAR frontend and Reveaal //! -//! The project is currently being developed at [Github](https://github.com/ECDAR-AAU-SW-P5/) -//! Ecdar-API serves as the intermediary between the [Ecdar frontend](https://github.com/ECDAR-AAU-SW-P5/Ecdar-GUI-Web) and the [Ecdar backend](https://github.com/ECDAR-AAU-SW-P5/Reveaal) (Reveaal). Its core functionality revolves around storing and managing entities such as users and projects, allowing the backend to focus solely on computations. +//! The project is currently being developed at [Github](https://github.com/Ecdar/) +//! Ecdar-API serves as the intermediary between the [Ecdar frontend](https://github.com/Ecdar/Ecdar-GUI-Web) and the [Ecdar backend](https://github.com/Ecdar/Reveaal) (Reveaal). Its core functionality revolves around storing and managing entities such as users and projects, allowing the backend to focus solely on computations. //! //! # Notes //! Currently, the only supported databases are `PostgreSQL` and `SQLite` @@ -12,9 +12,6 @@ mod controllers; mod entities; mod services; -use crate::contexts::*; -use crate::controllers::*; -use crate::services::{HashingService, ReveaalService, ServiceCollection}; use api::server::start_grpc_server; use dotenv::dotenv; use sea_orm::{ConnectionTrait, Database, DbBackend}; @@ -24,6 +21,13 @@ use std::sync::Arc; #[tokio::main] async fn main() -> Result<(), Box> { + use crate::contexts::{ + AccessContext, ContextCollection, DatabaseContextTrait, InUseContext, + PostgresDatabaseContext, ProjectContext, QueryContext, SQLiteDatabaseContext, + SessionContext, UserContext, + }; + use crate::controllers::*; + use crate::services::{HashingService, ReveaalService, ServiceCollection}; dotenv().ok(); let reveaal_addr = env::var("REVEAAL_ADDRESS").expect("Expected REVEAAL_ADDRESS to be set."); diff --git a/src/services/reveaal_service.rs b/src/services/reveaal_service.rs index 221e583..9656831 100644 --- a/src/services/reveaal_service.rs +++ b/src/services/reveaal_service.rs @@ -1,11 +1,9 @@ -use crate::api::server::protobuf::ecdar_backend_client::EcdarBackendClient; use crate::api::server::protobuf::{ - QueryRequest, QueryResponse, SimulationStartRequest, SimulationStepRequest, - SimulationStepResponse, UserTokenResponse, + ecdar_backend_client::EcdarBackendClient, QueryRequest, QueryResponse, SimulationStartRequest, + SimulationStepRequest, SimulationStepResponse, UserTokenResponse, }; use async_trait::async_trait; -use tonic::transport::Channel; -use tonic::{Request, Response, Status}; +use tonic::{transport::Channel, Request, Response, Status}; #[async_trait] pub trait ReveaalServiceTrait: Send + Sync { From 4d958898bd8a39f7eff61d90930f6956ecccfe03 Mon Sep 17 00:00:00 2001 From: Thomas Lohse Date: Mon, 26 Feb 2024 14:07:34 +0100 Subject: [PATCH 4/6] minor fixes --- src/api/auth.rs | 43 +++--- src/api/ecdar_api.rs | 1 + src/api/server.rs | 4 +- src/contexts/access_context.rs | 16 +-- ...e_context_trait.rs => database_context.rs} | 0 src/contexts/db_centexts/mod.rs | 12 +- ...atabase_context.rs => postgres_context.rs} | 11 +- ..._database_context.rs => sqlite_context.rs} | 14 +- src/contexts/entity_context_trait.rs | 8 +- src/contexts/in_use_context.rs | 26 ++-- src/contexts/project_context.rs | 72 +++++------ src/contexts/query_context.rs | 26 ++-- src/contexts/session_context.rs | 31 ++--- src/contexts/user_context.rs | 26 ++-- src/controllers/access_controller.rs | 122 +++++++----------- src/controllers/mod.rs | 2 +- src/controllers/project_controller.rs | 77 +++++------ src/controllers/query_controller.rs | 94 +++++--------- src/controllers/session_controller.rs | 28 ++-- src/controllers/user_controller.rs | 40 +++--- src/main.rs | 4 +- src/services/hashing_service.rs | 6 +- src/services/service_collection.rs | 4 +- 23 files changed, 293 insertions(+), 374 deletions(-) rename src/contexts/db_centexts/{database_context_trait.rs => database_context.rs} (100%) rename src/contexts/db_centexts/{postgres_database_context.rs => postgres_context.rs} (72%) rename src/contexts/db_centexts/{sqlite_database_context.rs => sqlite_context.rs} (68%) diff --git a/src/api/auth.rs b/src/api/auth.rs index d9eebc0..9182ac3 100644 --- a/src/api/auth.rs +++ b/src/api/auth.rs @@ -3,6 +3,7 @@ use jsonwebtoken::{ decode, encode, Algorithm, DecodingKey, EncodingKey, Header, TokenData, Validation, }; +use jsonwebtoken::errors::{Error, ErrorKind}; use serde::{Deserialize, Serialize}; use std::{env, fmt::Display, str::FromStr}; use tonic::{ @@ -12,12 +13,23 @@ use tonic::{ /// This method is used to validate the access token (not refresh). pub fn validation_interceptor(mut req: Request<()>) -> Result, Status> { - let token = match req.token_string().map_err(|err| { + /* + let token = match req.token_string().map_err(|err| Status::internal(format!( "could not stringify user id in request metadata, internal error {}", err )) - })? { + */ + let token = match req + .token_string() + .map_err(|e| { + format!( + "could not stringify user id in request metadata, internal error {}", + e + ) + }) + .map_err(Status::internal)? + { Some(token) => Token::from_str(TokenType::AccessToken, token), None => return Err(Status::unauthenticated("Token not found")), }; @@ -104,7 +116,7 @@ impl Token { /// /// let token = Token::new(TokenType::AccessToken, "1").unwrap(); /// ``` - pub fn new(token_type: TokenType, uid: &str) -> Result { + pub fn new>(token_type: TokenType, uid: T) -> Result { let now = Utc::now(); let expiration = now .checked_add_signed(token_type.duration()) @@ -112,7 +124,7 @@ impl Token { .timestamp(); let claims = Claims { - sub: uid.to_owned(), + sub: uid.into(), exp: expiration as usize, }; @@ -140,7 +152,7 @@ impl Token { /// /// assert_eq!(refresh_token.token_type(), TokenType::RefreshToken); /// ``` - pub fn refresh(uid: &str) -> Result { + pub fn refresh>(uid: T) -> Result { Token::new(TokenType::RefreshToken, uid) } @@ -157,7 +169,7 @@ impl Token { /// /// assert_eq!(access_token.token_type(), TokenType::AccessToken); /// ``` - pub fn access(uid: &str) -> Result { + pub fn access>(uid: T) -> Result { Token::new(TokenType::AccessToken, uid) } @@ -225,21 +237,21 @@ pub enum TokenError { Unknown(String), } -/// This is used to convert a [jsonwebtoken::errors::ErrorKind] to a [TokenError]. -impl From for TokenError { - fn from(error_kind: jsonwebtoken::errors::ErrorKind) -> Self { +/// This is used to convert a [ErrorKind] to a [TokenError]. +impl From for TokenError { + fn from(error_kind: ErrorKind) -> Self { match error_kind { - jsonwebtoken::errors::ErrorKind::InvalidToken => TokenError::InvalidToken, - jsonwebtoken::errors::ErrorKind::InvalidSignature => TokenError::InvalidSignature, - jsonwebtoken::errors::ErrorKind::ExpiredSignature => TokenError::ExpiredSignature, + ErrorKind::InvalidToken => TokenError::InvalidToken, + ErrorKind::InvalidSignature => TokenError::InvalidSignature, + ErrorKind::ExpiredSignature => TokenError::ExpiredSignature, _ => TokenError::Unknown("Unknown token error".to_string()), } } } -/// This is used to convert a [jsonwebtoken::errors::Error] to a [TokenError]. -impl From for TokenError { - fn from(error: jsonwebtoken::errors::Error) -> Self { +/// This is used to convert a [Error] to a [TokenError]. +impl From for TokenError { + fn from(error: Error) -> Self { TokenError::from(error.kind().clone()) } } @@ -290,7 +302,6 @@ impl RequestExt for Request { } } -#[cfg(test)] #[cfg(test)] mod tests { use crate::api::auth::{RequestExt, Token, TokenError, TokenType}; diff --git a/src/api/ecdar_api.rs b/src/api/ecdar_api.rs index 4836753..cdd03da 100644 --- a/src/api/ecdar_api.rs +++ b/src/api/ecdar_api.rs @@ -19,6 +19,7 @@ impl ConcreteEcdarApi { /// The macro can be found in the `ecdar_api_macros` crate. #[ecdar_api::endpoints] mod routes { + // TODO: Sometime maybe update it such that some endpoints are combined (queries, users, access, etc) to one endpoint (update project perhaps), but it takes an object with the updates use super::super::server::protobuf::{ ecdar_api_auth_server::EcdarApiAuth, ecdar_api_server::EcdarApi, ecdar_backend_server::EcdarBackend, CreateAccessRequest, CreateProjectRequest, diff --git a/src/api/server.rs b/src/api/server.rs index 5b7af62..b4cc94e 100644 --- a/src/api/server.rs +++ b/src/api/server.rs @@ -12,7 +12,7 @@ use crate::controllers::ControllerCollection; pub mod protobuf { tonic::include_proto!("ecdar_proto_buf"); } -#[allow(clippy::expect_used)] + pub async fn start_grpc_server( controllers: ControllerCollection, ) -> Result<(), Box> { @@ -33,7 +33,7 @@ pub async fn start_grpc_server( svc.clone(), auth::validation_interceptor, )) - .add_service(EcdarBackendServer::new(svc.clone())) + .add_service(EcdarBackendServer::new(svc)) .serve(addr) .await?; Ok(()) diff --git a/src/contexts/access_context.rs b/src/contexts/access_context.rs index 2687363..ca224fc 100644 --- a/src/contexts/access_context.rs +++ b/src/contexts/access_context.rs @@ -78,8 +78,8 @@ impl EntityContextTrait for AccessContext { project_id: Set(entity.project_id), user_id: Set(entity.user_id), }; - let access: access::Model = access.insert(&self.db_context.get_connection()).await?; - Ok(access) + access.insert(&self.db_context.get_connection()).await + //Ok(access.insert(&self.db_context.get_connection()).await?) } /// Returns a single access entity (uses primary key) @@ -131,17 +131,15 @@ impl EntityContextTrait for AccessContext { .await } - /// Deletes a access entity by id + /// Deletes an access entity by id async fn delete(&self, entity_id: i32) -> Result { let access = self.get_by_id(entity_id).await?; match access { None => Err(DbErr::RecordNotFound("No record was deleted".into())), - Some(access) => { - access::Entity::delete_by_id(entity_id) - .exec(&self.db_context.get_connection()) - .await?; - Ok(access) - } + Some(access) => access::Entity::delete_by_id(entity_id) + .exec(&self.db_context.get_connection()) + .await + .map(|_| access), } } } diff --git a/src/contexts/db_centexts/database_context_trait.rs b/src/contexts/db_centexts/database_context.rs similarity index 100% rename from src/contexts/db_centexts/database_context_trait.rs rename to src/contexts/db_centexts/database_context.rs diff --git a/src/contexts/db_centexts/mod.rs b/src/contexts/db_centexts/mod.rs index be7e519..78bcc9f 100644 --- a/src/contexts/db_centexts/mod.rs +++ b/src/contexts/db_centexts/mod.rs @@ -1,7 +1,7 @@ -pub mod database_context_trait; -pub mod postgres_database_context; -pub mod sqlite_database_context; +pub mod database_context; +pub mod postgres_context; +pub mod sqlite_context; -pub use database_context_trait::DatabaseContextTrait; -pub use postgres_database_context::PostgresDatabaseContext; -pub use sqlite_database_context::SQLiteDatabaseContext; +pub use database_context::DatabaseContextTrait; +pub use postgres_context::PostgresDatabaseContext; +pub use sqlite_context::SQLiteDatabaseContext; diff --git a/src/contexts/db_centexts/postgres_database_context.rs b/src/contexts/db_centexts/postgres_context.rs similarity index 72% rename from src/contexts/db_centexts/postgres_database_context.rs rename to src/contexts/db_centexts/postgres_context.rs index 954c552..76b3383 100644 --- a/src/contexts/db_centexts/postgres_database_context.rs +++ b/src/contexts/db_centexts/postgres_context.rs @@ -9,15 +9,14 @@ pub struct PostgresDatabaseContext { pub(crate) db_connection: DatabaseConnection, } impl PostgresDatabaseContext { - pub async fn new(connection_string: &str) -> Result { + pub async fn new(connection_string: &str) -> Result { let db = Database::connect(connection_string).await?; - let db = match db.get_database_backend() { - DbBackend::Postgres => db, - _ => panic!("Expected postgresql connection string"), - }; + if db.get_database_backend() != DbBackend::Postgres { + panic!("Expected postgresql connection string"); + } - Ok(PostgresDatabaseContext { db_connection: db }) + Ok(Self { db_connection: db }) } } diff --git a/src/contexts/db_centexts/sqlite_database_context.rs b/src/contexts/db_centexts/sqlite_context.rs similarity index 68% rename from src/contexts/db_centexts/sqlite_database_context.rs rename to src/contexts/db_centexts/sqlite_context.rs index 60fadfc..91a6e84 100644 --- a/src/contexts/db_centexts/sqlite_database_context.rs +++ b/src/contexts/db_centexts/sqlite_context.rs @@ -2,7 +2,6 @@ use crate::contexts::DatabaseContextTrait; use migration::{Migrator, MigratorTrait}; use sea_orm::prelude::async_trait::async_trait; use sea_orm::{ConnectionTrait, Database, DatabaseConnection, DbBackend, DbErr}; -use std::fmt::Debug; use std::sync::Arc; #[derive(Debug)] @@ -11,15 +10,14 @@ pub struct SQLiteDatabaseContext { } impl SQLiteDatabaseContext { - pub async fn new(connection_string: &str) -> Result { + pub async fn new(connection_string: &str) -> Result { let db = Database::connect(connection_string).await?; - let db = match db.get_database_backend() { - DbBackend::Sqlite => db, - _ => panic!("Expected sqlite connection string"), - }; + if db.get_database_backend() != DbBackend::Sqlite { + panic!("Expected sqlite connection string"); + } - Ok(SQLiteDatabaseContext { db_connection: db }) + Ok(Self { db_connection: db }) } } @@ -28,7 +26,7 @@ impl DatabaseContextTrait for SQLiteDatabaseContext { async fn reset(&self) -> Result, DbErr> { Migrator::fresh(&self.db_connection).await?; - Ok(Arc::new(SQLiteDatabaseContext { + Ok(Arc::new(Self { db_connection: self.get_connection(), })) } diff --git a/src/contexts/entity_context_trait.rs b/src/contexts/entity_context_trait.rs index 5bbb3f9..e018e1b 100644 --- a/src/contexts/entity_context_trait.rs +++ b/src/contexts/entity_context_trait.rs @@ -1,12 +1,12 @@ //! The base trait for all database entities. Exposes basic CRUD functionality for. -//! Some specific entities might need additional functionality, but that should implemented in entity-specific traits. +//! Some specific entities might need additional functionality, but that should be implemented in entity-specific traits. use sea_orm::prelude::async_trait::async_trait; -use sea_orm::DbErr; +use sea_orm::{DbErr, ModelTrait}; #[async_trait] /// The base trait for all database entities. Exposes basic CRUD functionality for. -/// Some specific entities might need additional functionality, but that should implemented in entity-specific traits. -pub trait EntityContextTrait: Send + Sync { +/// Some specific entities might need additional functionality, but that should be implemented in entity-specific traits. +pub trait EntityContextTrait: Send + Sync { /// Inserts an entity into the database /// # Errors /// Errors on failed connection, execution error or constraint violations. diff --git a/src/contexts/in_use_context.rs b/src/contexts/in_use_context.rs index 330361c..56b7142 100644 --- a/src/contexts/in_use_context.rs +++ b/src/contexts/in_use_context.rs @@ -22,13 +22,13 @@ impl InUseContext { impl EntityContextTrait for InUseContext { /// Used for creating a Model entity async fn create(&self, entity: in_use::Model) -> Result { - let in_use = in_use::ActiveModel { + in_use::ActiveModel { project_id: Set(entity.project_id), session_id: Set(entity.session_id), latest_activity: Set(Utc::now().naive_local()), - }; - let in_use: in_use::Model = in_use.insert(&self.db_context.get_connection()).await?; - Ok(in_use) + } + .insert(&self.db_context.get_connection()) + .await } async fn get_by_id(&self, entity_id: i32) -> Result, DbErr> { @@ -54,16 +54,14 @@ impl EntityContextTrait for InUseContext { } async fn delete(&self, entity_id: i32) -> Result { - let in_use = self.get_by_id(entity_id).await?; - match in_use { - None => Err(DbErr::RecordNotFound("No record was deleted".into())), - Some(in_use) => { - in_use::Entity::delete_by_id(entity_id) - .exec(&self.db_context.get_connection()) - .await?; - Ok(in_use) - } - } + let model = self + .get_by_id(entity_id) + .await? + .ok_or(DbErr::RecordNotFound("No record was deleted".into()))?; + in_use::Entity::delete_by_id(entity_id) + .exec(&self.db_context.get_connection()) + .await?; + Ok(model) } } diff --git a/src/contexts/project_context.rs b/src/contexts/project_context.rs index 8f4ab3d..7be8683 100644 --- a/src/contexts/project_context.rs +++ b/src/contexts/project_context.rs @@ -63,14 +63,14 @@ impl EntityContextTrait for ProjectContext { /// project_context.create(project); /// ``` async fn create(&self, entity: project::Model) -> Result { - let project = project::ActiveModel { + project::ActiveModel { id: Default::default(), name: Set(entity.name), components_info: Set(entity.components_info), owner_id: Set(entity.owner_id), - }; - let project: project::Model = project.insert(&self.db_context.get_connection()).await?; - Ok(project) + } + .insert(&self.db_context.get_connection()) + .await } /// Returns a single project entity (Uses primary key) @@ -109,30 +109,28 @@ impl EntityContextTrait for ProjectContext { /// let project = project_context.update(update_project).unwrap(); /// ``` async fn update(&self, entity: project::Model) -> Result { - let existing_project = self.get_by_id(entity.id).await?; - - return match existing_project { - None => Err(DbErr::RecordNotUpdated), - Some(existing_project) => { - let queries: Vec = existing_project - .find_related(query::Entity) - .all(&self.db_context.get_connection()) - .await?; - for q in queries.iter() { - let mut aq = q.clone().into_active_model(); - aq.outdated = Set(true); - aq.update(&self.db_context.get_connection()).await?; - } - project::ActiveModel { - id: Unchanged(entity.id), - name: Set(entity.name), - components_info: Set(entity.components_info), - owner_id: Unchanged(entity.id), - } - .update(&self.db_context.get_connection()) - .await - } - }; + let existing_project = self + .get_by_id(entity.id) + .await? + .ok_or(DbErr::RecordNotUpdated)?; + + let queries: Vec = existing_project + .find_related(query::Entity) + .all(&self.db_context.get_connection()) + .await?; + for q in queries.iter() { + let mut aq = q.clone().into_active_model(); + aq.outdated = Set(true); + aq.update(&self.db_context.get_connection()).await?; + } + project::ActiveModel { + id: Unchanged(entity.id), + name: Set(entity.name), + components_info: Set(entity.components_info), + owner_id: Unchanged(entity.id), + } + .update(&self.db_context.get_connection()) + .await } /// Returns and deletes a single project entity @@ -142,16 +140,14 @@ impl EntityContextTrait for ProjectContext { /// let project = project_context.delete().unwrap(); /// ``` async fn delete(&self, entity_id: i32) -> Result { - let project = self.get_by_id(entity_id).await?; - match project { - None => Err(DbErr::RecordNotFound("No record was deleted".into())), - Some(project) => { - project::Entity::delete_by_id(entity_id) - .exec(&self.db_context.get_connection()) - .await?; - Ok(project) - } - } + let project = self + .get_by_id(entity_id) + .await? + .ok_or(DbErr::RecordNotFound("No record was deleted".into()))?; + project::Entity::delete_by_id(entity_id) + .exec(&self.db_context.get_connection()) + .await?; + Ok(project) } } diff --git a/src/contexts/query_context.rs b/src/contexts/query_context.rs index fe77d78..152e81b 100644 --- a/src/contexts/query_context.rs +++ b/src/contexts/query_context.rs @@ -46,15 +46,15 @@ impl EntityContextTrait for QueryContext { /// context.create(model); /// ``` async fn create(&self, entity: query::Model) -> Result { - let query = query::ActiveModel { + query::ActiveModel { id: Default::default(), string: Set(entity.string), project_id: Set(entity.project_id), result: NotSet, outdated: NotSet, - }; - let query = query.insert(&self.db_context.get_connection()).await?; - Ok(query) + } + .insert(&self.db_context.get_connection()) + .await } /// Returns a single query entity (uses primary key) @@ -118,16 +118,14 @@ impl EntityContextTrait for QueryContext { } async fn delete(&self, entity_id: i32) -> Result { - let query = self.get_by_id(entity_id).await?; - match query { - None => Err(DbErr::RecordNotFound("No record was deleted".into())), - Some(query) => { - query::Entity::delete_by_id(entity_id) - .exec(&self.db_context.get_connection()) - .await?; - Ok(query) - } - } + let query = self + .get_by_id(entity_id) + .await? + .ok_or(DbErr::RecordNotFound("No record was deleted".into()))?; + query::Entity::delete_by_id(entity_id) + .exec(&self.db_context.get_connection()) + .await?; + Ok(query) } } diff --git a/src/contexts/session_context.rs b/src/contexts/session_context.rs index 77b37d5..b41c50f 100644 --- a/src/contexts/session_context.rs +++ b/src/contexts/session_context.rs @@ -69,9 +69,8 @@ impl SessionContextTrait for SessionContext { session::Entity::delete_by_id(session.id) .exec(&self.db_context.get_connection()) - .await?; - - Ok(session) + .await + .map(|_| session) } } @@ -97,15 +96,15 @@ impl EntityContextTrait for SessionContext { /// let created_session = session_context.create(model).await.unwrap(); /// ``` async fn create(&self, entity: session::Model) -> Result { - let session = session::ActiveModel { + session::ActiveModel { id: Default::default(), refresh_token: Set(entity.refresh_token), access_token: Set(entity.access_token), user_id: Set(entity.user_id), updated_at: NotSet, - }; - - session.insert(&self.db_context.get_connection()).await + } + .insert(&self.db_context.get_connection()) + .await } /// Returns a session by searching for its id. @@ -183,16 +182,14 @@ impl EntityContextTrait for SessionContext { /// |----|-------|------------|---------| /// | | | | | async fn delete(&self, id: i32) -> Result { - let session = self.get_by_id(id).await?; - match session { - None => Err(DbErr::RecordNotFound("No record was deleted".into())), - Some(session) => { - session::Entity::delete_by_id(id) - .exec(&self.db_context.get_connection()) - .await?; - Ok(session) - } - } + let session = self + .get_by_id(id) + .await? + .ok_or(DbErr::RecordNotFound("No record was deleted".into()))?; + session::Entity::delete_by_id(id) + .exec(&self.db_context.get_connection()) + .await + .map(|_| session) } } diff --git a/src/contexts/user_context.rs b/src/contexts/user_context.rs index cf390d8..a44dd84 100644 --- a/src/contexts/user_context.rs +++ b/src/contexts/user_context.rs @@ -77,14 +77,14 @@ impl EntityContextTrait for UserContext { /// context.create(user); /// ``` async fn create(&self, entity: user::Model) -> Result { - let user = user::ActiveModel { + user::ActiveModel { id: Default::default(), email: Set(entity.email), username: Set(entity.username), password: Set(entity.password), - }; - let user = user.insert(&self.db_context.get_connection()).await?; - Ok(user) + } + .insert(&self.db_context.get_connection()) + .await } /// Returns a single user entity (uses primary key) @@ -157,16 +157,14 @@ impl EntityContextTrait for UserContext { /// password: user.password /// } async fn delete(&self, entity_id: i32) -> Result { - let user = self.get_by_id(entity_id).await?; - match user { - None => Err(DbErr::RecordNotFound("No record was deleted".into())), - Some(user) => { - user::Entity::delete_by_id(entity_id) - .exec(&self.db_context.get_connection()) - .await?; - Ok(user) - } - } + let user = self + .get_by_id(entity_id) + .await? + .ok_or(DbErr::RecordNotFound("No record was deleted".into()))?; + user::Entity::delete_by_id(entity_id) + .exec(&self.db_context.get_connection()) + .await + .map(|_| user) } } diff --git a/src/controllers/access_controller.rs b/src/controllers/access_controller.rs index c5fa96b..6d47805 100644 --- a/src/controllers/access_controller.rs +++ b/src/controllers/access_controller.rs @@ -8,7 +8,7 @@ use crate::contexts::{AccessContextTrait, ContextCollection, UserContextTrait}; use crate::entities::{access, user}; use async_trait::async_trait; use std::sync::Arc; -use tonic::{Code, Request, Response, Status}; +use tonic::{Request, Response, Status}; #[async_trait] pub trait AccessControllerTrait: Send + Sync { @@ -19,7 +19,7 @@ pub trait AccessControllerTrait: Send + Sync { &self, request: Request, ) -> Result, Status>; - /// Creates an access in the contexts. + /// Creates access in the contexts. /// # Errors /// Returns an error if the contexts context fails to create the access async fn create_access( @@ -76,41 +76,26 @@ impl AccessControllerTrait for AccessController { })? .ok_or(Status::internal("Could not get uid from request metadata"))?; - match self - .contexts + self.contexts .access_context .get_access_by_uid_and_project_id(uid, message.project_id) .await - { - Ok(access) => { - if access.is_none() { - return Err(Status::new( - Code::PermissionDenied, - "User does not have access to model", - )); - } - } - Err(error) => return Err(Status::new(Code::Internal, error.to_string())), - }; - - match self - .contexts + .map_err(|error| Status::internal(error.to_string()))? + .ok_or(Status::permission_denied( + "User does not have access to model", + ))?; + self.contexts .access_context .get_access_by_project_id(message.project_id) .await - { - Ok(access_info_list) => { + .map_err(|error| Status::internal(error.to_string())) + .and_then(|access_info_list| { if access_info_list.is_empty() { - return Err(Status::new( - Code::NotFound, - "No access found for given user", - )); + Err(Status::not_found("No access found for given user")) } else { Ok(Response::new(ListAccessInfoResponse { access_info_list })) } - } - Err(error) => Err(Status::new(Code::Internal, error.to_string())), - } + }) } async fn create_access( @@ -151,13 +136,10 @@ impl AccessControllerTrait for AccessController { match self.contexts.access_context.create(access).await { Ok(_) => Ok(Response::new(())), - Err(error) => Err(Status::new(Code::Internal, error.to_string())), + Err(error) => Err(Status::internal(error.to_string())), } } else { - Err(Status::new( - Code::InvalidArgument, - "No user identification provided", - )) + Err(Status::invalid_argument("No user identification provided")) } } @@ -182,13 +164,8 @@ impl AccessControllerTrait for AccessController { .access_context .get_by_id(message.id) .await - .map_err(|err| Status::new(Code::Internal, err.to_string()))? - .ok_or_else(|| { - Status::new( - Code::NotFound, - "No access entity found for user".to_string(), - ) - })?; + .map_err(|err| Status::internal(err.to_string()))? + .ok_or_else(|| Status::not_found("No access entity found for user".to_string()))?; check_editor_role_helper( Arc::clone(&self.contexts.access_context), @@ -202,13 +179,12 @@ impl AccessControllerTrait for AccessController { .project_context .get_by_id(user_access.project_id) .await - .map_err(|err| Status::new(Code::Internal, err.to_string()))? - .ok_or_else(|| Status::new(Code::NotFound, "No model found for access".to_string()))?; + .map_err(|err| Status::internal(err.to_string()))? + .ok_or_else(|| Status::not_found("No model found for access".to_string()))?; // Check that the requester is not trying to update the owner's access if model.owner_id == message.id { - return Err(Status::new( - Code::PermissionDenied, + return Err(Status::permission_denied( "Requester does not have permission to update access for this user", )); } @@ -220,10 +196,12 @@ impl AccessControllerTrait for AccessController { user_id: Default::default(), }; - match self.contexts.access_context.update(access).await { - Ok(_) => Ok(Response::new(())), - Err(error) => Err(Status::new(Code::Internal, error.to_string())), - } + self.contexts + .access_context + .update(access) + .await + .map(|_| Response::new(())) + .map_err(|error| Status::internal(error.to_string())) } async fn delete_access( @@ -247,13 +225,8 @@ impl AccessControllerTrait for AccessController { .access_context .get_by_id(message.id) .await - .map_err(|err| Status::new(Code::Internal, err.to_string()))? - .ok_or_else(|| { - Status::new( - Code::NotFound, - "No access entity found for user".to_string(), - ) - })?; + .map_err(|err| Status::internal(err.to_string()))? + .ok_or_else(|| Status::not_found("No access entity found for user".to_string()))?; check_editor_role_helper( Arc::clone(&self.contexts.access_context), @@ -267,13 +240,12 @@ impl AccessControllerTrait for AccessController { .project_context .get_by_id(user_access.project_id) .await - .map_err(|err| Status::new(Code::Internal, err.to_string()))? - .ok_or_else(|| Status::new(Code::NotFound, "No model found for access".to_string()))?; + .map_err(|err| Status::internal(err.to_string()))? + .ok_or_else(|| Status::not_found("No model found for access".to_string()))?; // Check that the requester is not trying to delete the owner's access if model.owner_id == message.id { - return Err(Status::new( - Code::PermissionDenied, + return Err(Status::permission_denied( "You cannot delete the access entity for this user", )); } @@ -281,10 +253,8 @@ impl AccessControllerTrait for AccessController { match self.contexts.access_context.delete(message.id).await { Ok(_) => Ok(Response::new(())), Err(error) => match error { - sea_orm::DbErr::RecordNotFound(message) => { - Err(Status::new(Code::NotFound, message)) - } - _ => Err(Status::new(Code::Internal, error.to_string())), + sea_orm::DbErr::RecordNotFound(message) => Err(Status::not_found(message)), + _ => Err(Status::internal(error.to_string())), }, } } @@ -297,23 +267,19 @@ async fn check_editor_role_helper( let access = access_context .get_access_by_uid_and_project_id(user_id, project_id) .await - .map_err(|err| Status::new(Code::Internal, err.to_string()))? + .map_err(|err| Status::internal(err.to_string()))? .ok_or_else(|| { - Status::new( - Code::PermissionDenied, - "User does not have access to model".to_string(), - ) + Status::permission_denied("User does not have access to model".to_string()) })?; // Check if the requester has role 'Editor' if access.role != "Editor" { - return Err(Status::new( - Code::PermissionDenied, + Err(Status::permission_denied( "User does not have 'Editor' role for this model", - )); + )) + } else { + Ok(()) } - - Ok(()) } async fn create_access_find_user_helper( @@ -324,20 +290,20 @@ async fn create_access_find_user_helper( User::UserId(user_id) => Ok(user_context .get_by_id(user_id) .await - .map_err(|err| Status::new(Code::Internal, err.to_string()))? - .ok_or_else(|| Status::new(Code::NotFound, "No user found with given id"))?), + .map_err(|err| Status::internal(err.to_string()))? + .ok_or_else(|| Status::not_found("No user found with given id"))?), User::Username(username) => Ok(user_context .get_by_username(username) .await - .map_err(|err| Status::new(Code::Internal, err.to_string()))? - .ok_or_else(|| Status::new(Code::NotFound, "No user found with given username"))?), + .map_err(|err| Status::internal(err.to_string()))? + .ok_or_else(|| Status::not_found("No user found with given username"))?), User::Email(email) => Ok(user_context .get_by_email(email) .await - .map_err(|err| Status::new(Code::Internal, err.to_string()))? - .ok_or_else(|| Status::new(Code::NotFound, "No user found with given email"))?), + .map_err(|err| Status::internal(err.to_string()))? + .ok_or_else(|| Status::not_found("No user found with given email"))?), } } diff --git a/src/controllers/mod.rs b/src/controllers/mod.rs index 100b38a..46c87b4 100644 --- a/src/controllers/mod.rs +++ b/src/controllers/mod.rs @@ -199,7 +199,7 @@ mod helpers { mock! { pub HashingService {} - impl HashingServiceTrait for HashingService { + impl HashingService for HashingService { fn hash_password(&self, password: String) -> Result; fn verify_password(&self, password: String, hash: &str) -> Result; } diff --git a/src/controllers/project_controller.rs b/src/controllers/project_controller.rs index 277f192..3d80880 100644 --- a/src/controllers/project_controller.rs +++ b/src/controllers/project_controller.rs @@ -8,7 +8,7 @@ use crate::entities::{access, in_use, project}; use async_trait::async_trait; use chrono::{Duration, Utc}; use sea_orm::SqlErr; -use tonic::{Code, Request, Response, Status}; +use tonic::{Request, Response, Status}; const IN_USE_DURATION_MINUTES: i64 = 10; @@ -73,7 +73,7 @@ impl ProjectControllerTrait for ProjectController { &self, request: Request, ) -> Result, Status> { - let message = request.get_ref().clone(); + let message = request.get_ref(); let project_id = message.id; @@ -92,21 +92,16 @@ impl ProjectControllerTrait for ProjectController { .access_context .get_access_by_uid_and_project_id(uid, project_id) .await - .map_err(|err| Status::new(Code::Internal, err.to_string()))? - .ok_or_else(|| { - Status::new( - Code::PermissionDenied, - "User does not have access to project", - ) - })?; + .map_err(|err| Status::internal(err.to_string()))? + .ok_or_else(|| Status::permission_denied("User does not have access to project"))?; let project = self .contexts .project_context .get_by_id(project_id) .await - .map_err(|err| Status::new(Code::Internal, err.to_string()))? - .ok_or_else(|| Status::new(Code::Internal, "Model not found"))?; + .map_err(|err| Status::internal(err.to_string()))? + .ok_or_else(|| Status::internal("Model not found"))?; let project = Project { id: project.id, @@ -141,10 +136,9 @@ impl ProjectControllerTrait for ProjectController { .ok_or(Status::invalid_argument("failed to get token from request metadata"))?, ) .await - .map_err(|err| Status::new(Code::Internal, err.to_string()))? + .map_err(|err| Status::internal(err.to_string()))? .ok_or_else(|| { - Status::new( - Code::Unauthenticated, + Status::unauthenticated( "No session found with given access token", ) })?; @@ -159,12 +153,12 @@ impl ProjectControllerTrait for ProjectController { .in_use_context .update(in_use) .await - .map_err(|err| Status::new(Code::Internal, err.to_string()))?; + .map_err(|err| Status::internal(err.to_string()))?; } } } - Ok(None) => return Err(Status::new(Code::Internal, "No in use found for project")), - Err(err) => return Err(Status::new(Code::Internal, err.to_string())), + Ok(None) => return Err(Status::internal("No in use found for project")), + Err(err) => return Err(Status::internal(err.to_string())), } let queries = self @@ -172,7 +166,7 @@ impl ProjectControllerTrait for ProjectController { .query_context .get_all_by_project_id(project_id) .await - .map_err(|err| Status::new(Code::Internal, err.to_string()))?; + .map_err(|err| Status::internal(err.to_string()))?; let queries = queries .into_iter() @@ -327,11 +321,13 @@ impl ProjectControllerTrait for ProjectController { .ok_or(Status::internal("Could not get uid from request metadata"))?; // Check if the project exists - let project = match self.contexts.project_context.get_by_id(message.id).await { - Ok(Some(project)) => project, - Ok(None) => return Err(Status::not_found("No project found with given id")), - Err(error) => return Err(Status::internal(error.to_string())), - }; + let project = self + .contexts + .project_context + .get_by_id(message.id) + .await + .map_err(|error| Status::internal(error.to_string()))? + .ok_or(Status::not_found("No project found with given id"))?; // Check if the user has access to the project match self @@ -447,7 +443,7 @@ impl ProjectControllerTrait for ProjectController { match self.contexts.project_context.update(new_project).await { Ok(_) => Ok(Response::new(())), - Err(error) => Err(Status::new(Code::Internal, error.to_string())), + Err(error) => Err(Status::internal(error.to_string())), } } @@ -466,21 +462,17 @@ impl ProjectControllerTrait for ProjectController { .ok_or(Status::internal("Could not get uid from request metadata"))?; let project_id = request.get_ref().id; - let project = match self.contexts.project_context.get_by_id(project_id).await { - Ok(Some(project)) => project, - Ok(None) => { - return Err(Status::new( - Code::NotFound, - "No project found with given id", - )); - } - Err(err) => return Err(Status::new(Code::Internal, err.to_string())), - }; + let project = self + .contexts + .project_context + .get_by_id(project_id) + .await + .map_err(|err| Status::internal(err.to_string()))? + .ok_or(Status::not_found("No project found with given id"))?; // Check if user is owner and thereby has permission to delete project if project.owner_id != uid { - return Err(Status::new( - Code::PermissionDenied, + return Err(Status::permission_denied( "You do not have permission to delete this project", )); } @@ -488,10 +480,8 @@ impl ProjectControllerTrait for ProjectController { match self.contexts.project_context.delete(project_id).await { Ok(_) => Ok(Response::new(())), Err(error) => match error { - sea_orm::DbErr::RecordNotFound(message) => { - Err(Status::new(Code::NotFound, message)) - } - _ => Err(Status::new(Code::Internal, error.to_string())), + sea_orm::DbErr::RecordNotFound(message) => Err(Status::not_found(message)), + _ => Err(Status::internal(error.to_string())), }, } } @@ -518,17 +508,14 @@ impl ProjectControllerTrait for ProjectController { { Ok(project_info_list) => { if project_info_list.is_empty() { - return Err(Status::new( - Code::NotFound, - "No access found for given user", - )); + Err(Status::not_found("No access found for given user")) } else { Ok(Response::new(ListProjectsInfoResponse { project_info_list, })) } } - Err(error) => Err(Status::new(Code::Internal, error.to_string())), + Err(error) => Err(Status::internal(error.to_string())), } } } diff --git a/src/controllers/query_controller.rs b/src/controllers/query_controller.rs index 5164d4b..be22139 100644 --- a/src/controllers/query_controller.rs +++ b/src/controllers/query_controller.rs @@ -7,7 +7,7 @@ use crate::contexts::ContextCollection; use crate::entities::query; use crate::services::ServiceCollection; use async_trait::async_trait; -use tonic::{Code, Request, Response, Status}; +use tonic::{Request, Response, Status}; #[async_trait] pub trait QueryControllerTrait: Send + Sync { @@ -82,17 +82,11 @@ impl QueryControllerTrait for QueryController { query_request.project_id, ) .await - .map_err(|err| Status::new(Code::Internal, err.to_string()))? - .ok_or_else(|| { - Status::new( - Code::PermissionDenied, - "User does not have access to project", - ) - })?; + .map_err(|err| Status::internal(err.to_string()))? + .ok_or_else(|| Status::permission_denied("User does not have access to project"))?; if access.role != "Editor" { - return Err(Status::new( - Code::PermissionDenied, + return Err(Status::permission_denied( "Role does not have permission to create query", )); } @@ -105,10 +99,12 @@ impl QueryControllerTrait for QueryController { project_id: query_request.project_id, }; - match self.contexts.query_context.create(query).await { - Ok(_) => Ok(Response::new(())), - Err(error) => Err(Status::new(Code::Internal, error.to_string())), - } + self.contexts + .query_context + .create(query) + .await + .map(|_| Response::new(())) + .map_err(|e| Status::internal(e.to_string())) } async fn update_query( @@ -122,12 +118,9 @@ impl QueryControllerTrait for QueryController { .query_context .get_by_id(message.id) .await - .map_err(|err| Status::new(Code::Internal, err.to_string()))?; + .map_err(|err| Status::internal(err.to_string()))?; - let old_query = match old_query_res { - Some(oq) => oq, - None => return Err(Status::new(Code::NotFound, "Query not found".to_string())), - }; + let old_query = old_query_res.ok_or(Status::not_found("Query not found"))?; let access = self .contexts @@ -147,17 +140,11 @@ impl QueryControllerTrait for QueryController { old_query.project_id, ) .await - .map_err(|err| Status::new(Code::Internal, err.to_string()))? - .ok_or_else(|| { - Status::new( - Code::PermissionDenied, - "User does not have access to project", - ) - })?; + .map_err(|err| Status::internal(err.to_string()))? + .ok_or_else(|| Status::permission_denied("User does not have access to project"))?; if access.role != "Editor" { - return Err(Status::new( - Code::PermissionDenied, + return Err(Status::permission_denied( "Role does not have permission to update query", )); } @@ -170,10 +157,12 @@ impl QueryControllerTrait for QueryController { outdated: old_query.outdated, }; - match self.contexts.query_context.update(query).await { - Ok(_) => Ok(Response::new(())), - Err(error) => Err(Status::new(Code::Internal, error.to_string())), - } + self.contexts + .query_context + .update(query) + .await + .map(|_| Response::new(())) + .map_err(|e| Status::internal(e.to_string())) } async fn delete_query( @@ -187,8 +176,8 @@ impl QueryControllerTrait for QueryController { .query_context .get_by_id(message.id) .await - .map_err(|err| Status::new(Code::Internal, err.to_string()))? - .ok_or_else(|| Status::new(Code::NotFound, "Query not found"))?; + .map_err(|err| Status::internal(err.to_string()))? + .ok_or_else(|| Status::not_found("Query not found"))?; let access = self .contexts @@ -208,17 +197,11 @@ impl QueryControllerTrait for QueryController { query.project_id, ) .await - .map_err(|err| Status::new(Code::Internal, err.to_string()))? - .ok_or_else(|| { - Status::new( - Code::PermissionDenied, - "User does not have access to project", - ) - })?; + .map_err(|err| Status::internal(err.to_string()))? + .ok_or_else(|| Status::permission_denied("User does not have access to project"))?; if access.role != "Editor" { - return Err(Status::new( - Code::PermissionDenied, + return Err(Status::permission_denied( "Role does not have permission to update query", )); } @@ -226,10 +209,8 @@ impl QueryControllerTrait for QueryController { match self.contexts.query_context.delete(message.id).await { Ok(_) => Ok(Response::new(())), Err(error) => match error { - sea_orm::DbErr::RecordNotFound(message) => { - Err(Status::new(Code::NotFound, message)) - } - _ => Err(Status::new(Code::Internal, error.to_string())), + sea_orm::DbErr::RecordNotFound(message) => Err(Status::not_found(message)), + _ => Err(Status::internal(error.to_string())), }, } } @@ -257,13 +238,8 @@ impl QueryControllerTrait for QueryController { .access_context .get_access_by_uid_and_project_id(uid, message.project_id) .await - .map_err(|err| Status::new(Code::Internal, err.to_string()))? - .ok_or_else(|| { - Status::new( - Code::PermissionDenied, - "User does not have access to project", - ) - })?; + .map_err(|err| Status::internal(err.to_string()))? + .ok_or_else(|| Status::permission_denied("User does not have access to project"))?; // Get project from contexts let project = self @@ -271,8 +247,8 @@ impl QueryControllerTrait for QueryController { .project_context .get_by_id(message.project_id) .await - .map_err(|err| Status::new(Code::Internal, err.to_string()))? - .ok_or_else(|| Status::new(Code::NotFound, "Model not found"))?; + .map_err(|err| Status::internal(err.to_string()))? + .ok_or_else(|| Status::not_found("Model not found"))?; // Get query from contexts let query = self @@ -280,8 +256,8 @@ impl QueryControllerTrait for QueryController { .query_context .get_by_id(message.id) .await - .map_err(|err| Status::new(Code::Internal, err.to_string()))? - .ok_or_else(|| Status::new(Code::NotFound, "Query not found"))?; + .map_err(|err| Status::internal(err.to_string()))? + .ok_or_else(|| Status::not_found("Query not found"))?; // Construct query request to send to Reveaal let query_request = Request::new(QueryRequest { @@ -329,7 +305,7 @@ impl QueryControllerTrait for QueryController { project_id: query.project_id, }) .await - .map_err(|err| Status::new(Code::Internal, err.to_string()))?; + .map_err(|err| Status::internal(err.to_string()))?; Ok(Response::new(SendQueryResponse { response: Some(query_result.into_inner()), diff --git a/src/controllers/session_controller.rs b/src/controllers/session_controller.rs index 3e9cf92..f84df38 100644 --- a/src/controllers/session_controller.rs +++ b/src/controllers/session_controller.rs @@ -1,14 +1,14 @@ use crate::api::auth::{RequestExt, Token, TokenError, TokenType}; +use crate::api::server::protobuf::get_auth_token_request::user_credentials::User; use crate::api::server::protobuf::{ - get_auth_token_request::{user_credentials, UserCredentials}, - GetAuthTokenRequest, GetAuthTokenResponse, + get_auth_token_request::UserCredentials, GetAuthTokenRequest, GetAuthTokenResponse, }; use crate::contexts::ContextCollection; use crate::entities::{session, user}; use crate::services::ServiceCollection; use async_trait::async_trait; use sea_orm::DbErr; -use tonic::{Code, Request, Response, Status}; +use tonic::{Request, Response, Status}; #[async_trait] pub trait SessionControllerTrait: Send + Sync { @@ -43,13 +43,12 @@ impl SessionController { &self, user_credentials: UserCredentials, ) -> Result, DbErr> { - match user_credentials.user { - Some(user_credentials::User::Username(username)) => { - Ok(self.contexts.user_context.get_by_username(username).await?) - } - Some(user_credentials::User::Email(email)) => { - Ok(self.contexts.user_context.get_by_email(email).await?) - } + let m = user_credentials.user.map(|u| match u { + User::Username(username) => self.contexts.user_context.get_by_username(username), + User::Email(email) => self.contexts.user_context.get_by_email(email), + }); + match m { + Some(l) => Ok(l.await?), None => Ok(None), } } @@ -111,15 +110,12 @@ impl SessionControllerTrait for SessionController { })? .ok_or(Status::unauthenticated("No access token provided"))?; - match self - .contexts + self.contexts .session_context .delete_by_token(TokenType::AccessToken, access_token) .await - { - Ok(_) => Ok(Response::new(())), - Err(error) => Err(Status::new(Code::Internal, error.to_string())), - } + .map(|_| Response::new(())) + .map_err(|e| Status::internal(e.to_string())) } async fn get_auth_token( diff --git a/src/controllers/user_controller.rs b/src/controllers/user_controller.rs index f808d4e..02cd041 100644 --- a/src/controllers/user_controller.rs +++ b/src/controllers/user_controller.rs @@ -9,7 +9,7 @@ use crate::services::ServiceCollection; use async_trait::async_trait; use regex::Regex; use sea_orm::SqlErr; -use tonic::{Code, Request, Response, Status}; +use tonic::{Request, Response, Status}; #[async_trait] pub trait UserControllerTrait: Send + Sync { @@ -39,7 +39,6 @@ impl UserController { } /// Returns true if the given email is a valid format. - #[allow(clippy::expect_used)] fn is_valid_email(&self, email: &str) -> bool { Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$") .expect("failed to compile regex") @@ -47,7 +46,6 @@ impl UserController { } /// Returns true if the given username is a valid format, i.e. only contains letters and numbers and a length from 3 to 32. - #[allow(clippy::expect_used)] fn is_valid_username(&self, username: &str) -> bool { Regex::new(r"^[a-zA-Z0-9_]{3,32}$") .expect("failed to compile regex") @@ -64,11 +62,11 @@ impl UserControllerTrait for UserController { let message = request.into_inner().clone(); if !self.is_valid_username(message.clone().username.as_str()) { - return Err(Status::new(Code::InvalidArgument, "Invalid username")); + return Err(Status::invalid_argument("Invalid username")); } if !self.is_valid_email(message.clone().email.as_str()) { - return Err(Status::new(Code::InvalidArgument, "Invalid email")); + return Err(Status::invalid_argument("Invalid email")); } let hashed_password = self @@ -93,9 +91,9 @@ impl UserControllerTrait for UserController { _ if e.contains("email") => "A user with that email already exists", _ => "User already exists", }; - Err(Status::new(Code::AlreadyExists, error_msg)) + Err(Status::already_exists(error_msg)) } - _ => Err(Status::new(Code::Internal, "Could not create user")), + _ => Err(Status::internal("Could not create user")), }, } } @@ -126,33 +124,33 @@ impl UserControllerTrait for UserController { .user_context .get_by_id(uid) .await - .map_err(|err| Status::new(Code::Internal, err.to_string()))? - .ok_or_else(|| Status::new(Code::Internal, "No user found with given uid"))?; + .map_err(|err| Status::internal(err.to_string()))? + .ok_or_else(|| Status::internal("No user found with given uid"))?; // Record to be inserted in contexts let new_user = user::Model { id: uid, - username: match message.clone().username { + username: match message.username { Some(username) => { if self.is_valid_username(username.as_str()) { username } else { - return Err(Status::new(Code::InvalidArgument, "Invalid username")); + return Err(Status::invalid_argument("Invalid username")); } } None => user.username, }, - email: match message.clone().email { + email: match message.email { Some(email) => { if self.is_valid_email(email.as_str()) { email } else { - return Err(Status::new(Code::InvalidArgument, "Invalid email")); + return Err(Status::invalid_argument("Invalid email")); } } None => user.email, }, - password: match message.clone().password { + password: match message.password { Some(password) => self .services .hashing_service @@ -165,7 +163,7 @@ impl UserControllerTrait for UserController { // Update user in contexts match self.contexts.user_context.update(new_user).await { Ok(_) => Ok(Response::new(())), - Err(error) => Err(Status::new(Code::Internal, error.to_string())), + Err(error) => Err(Status::internal(error.to_string())), } } @@ -185,14 +183,16 @@ impl UserControllerTrait for UserController { .ok_or(Status::internal("Could not get uid from request metadata"))?; // Delete user from contexts - match self.contexts.user_context.delete(uid).await { - Ok(_) => Ok(Response::new(())), - Err(error) => Err(Status::new(Code::Internal, error.to_string())), - } + self.contexts + .user_context + .delete(uid) + .await + .map(|_| Response::new(())) + .map_err(|e| Status::internal(e.to_string())) } /// Gets users from the contexts. - /// If no users exits with the given ids, an empty list is returned. + /// If no users exist with the given ids, an empty list is returned. async fn get_users( &self, request: Request, diff --git a/src/main.rs b/src/main.rs index a9fdbcb..e17fef7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,7 +27,7 @@ async fn main() -> Result<(), Box> { SessionContext, UserContext, }; use crate::controllers::*; - use crate::services::{HashingService, ReveaalService, ServiceCollection}; + use crate::services::{DefaultHashing, ReveaalService, ServiceCollection}; dotenv().ok(); let reveaal_addr = env::var("REVEAAL_ADDRESS").expect("Expected REVEAAL_ADDRESS to be set."); @@ -50,7 +50,7 @@ async fn main() -> Result<(), Box> { }; let services = ServiceCollection { - hashing_service: Arc::new(HashingService), + hashing_service: Arc::new(DefaultHashing), reveaal_service: Arc::new(ReveaalService::new(&reveaal_addr)), }; diff --git a/src/services/hashing_service.rs b/src/services/hashing_service.rs index 2b679e5..d56b87f 100644 --- a/src/services/hashing_service.rs +++ b/src/services/hashing_service.rs @@ -1,13 +1,13 @@ use bcrypt::{hash, verify, BcryptError, DEFAULT_COST}; -pub trait HashingServiceTrait: Send + Sync { +pub trait HashingService: Send + Sync { fn hash_password(&self, password: String) -> Result; fn verify_password(&self, password: String, hash: &str) -> Result; } -pub struct HashingService; +pub struct DefaultHashing; -impl HashingServiceTrait for HashingService { +impl HashingService for DefaultHashing { fn hash_password(&self, password: String) -> Result { hash(password, DEFAULT_COST) } diff --git a/src/services/service_collection.rs b/src/services/service_collection.rs index 2d7d109..feee2ee 100644 --- a/src/services/service_collection.rs +++ b/src/services/service_collection.rs @@ -1,8 +1,8 @@ -use crate::services::{HashingServiceTrait, ReveaalServiceTrait}; +use crate::services::{HashingService, ReveaalServiceTrait}; use std::sync::Arc; #[derive(Clone)] pub struct ServiceCollection { - pub(crate) hashing_service: Arc, + pub(crate) hashing_service: Arc, pub(crate) reveaal_service: Arc, } From 1084aa7d3c1bf0ac1aa1f882d71b62c3aceb5e72 Mon Sep 17 00:00:00 2001 From: Thomas Lohse Date: Mon, 26 Feb 2024 16:07:02 +0100 Subject: [PATCH 5/6] fix naming in CI --- .github/workflows/build_artifacts.yml | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build_artifacts.yml b/.github/workflows/build_artifacts.yml index 7f3c478..e33dd72 100644 --- a/.github/workflows/build_artifacts.yml +++ b/.github/workflows/build_artifacts.yml @@ -30,7 +30,7 @@ jobs: args: --release - uses: actions/upload-artifact@v3 with: - name: reveaal-${{ matrix.os }} - path: ${{ runner.os == 'Windows' && 'target/release/reveaal.exe' || 'target/release/reveaal' }} + name: ecdar_api-${{ matrix.os }} + path: ${{ runner.os == 'Windows' && 'target/release/ecdar_api.exe' || 'target/release/ecdar_api' }} if-no-files-found: error retention-days: 7 diff --git a/Cargo.toml b/Cargo.toml index 5a0e77d..501b233 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "api_server" +name = "ecdar_api" version = "0.0.1" build = "src/build.rs" edition = "2021" From 8a7b6547dd66a2aff44c89a367e56da84d487e6b Mon Sep 17 00:00:00 2001 From: Thomas Lohse <49527735+t-lohse@users.noreply.github.com> Date: Wed, 13 Mar 2024 16:55:10 +0100 Subject: [PATCH 6/6] Update .gitmodules --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index d5f774f..6974b96 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "Ecdar-ProtoBuf"] path = Ecdar-ProtoBuf - url = git@github.com:Ecdar/Ecdar-ProtoBuf.git + url = https://github.com/Ecdar/Ecdar-ProtoBuf branch = SW5 # main