From c293929b110b1b1f8bc70b08ebddab0117d7e222 Mon Sep 17 00:00:00 2001 From: Zack Fu Zi Xiang Date: Wed, 6 Mar 2024 14:01:57 +0800 Subject: [PATCH] feat: added tests for inviting member which has not signed up yet --- libs/client-api-test-util/src/user.rs | 24 +++++++++++++ libs/shared-entity/src/dto/workspace_dto.rs | 2 +- src/api/workspace.rs | 2 ++ src/application.rs | 5 +-- src/biz/workspace/ops.rs | 37 ++++++++++++++++++++- src/main.rs | 2 +- tests/workspace/member_crud.rs | 34 +++++++++++++------ 7 files changed, 89 insertions(+), 17 deletions(-) diff --git a/libs/client-api-test-util/src/user.rs b/libs/client-api-test-util/src/user.rs index 2f6e08c7..5c3a2f1a 100644 --- a/libs/client-api-test-util/src/user.rs +++ b/libs/client-api-test-util/src/user.rs @@ -76,6 +76,30 @@ pub async fn generate_sign_in_action_link(email: &str) -> String { .unwrap() } +// same as generate_unique_registered_user_client +// but with specific email +pub async fn api_client_with_email(user_email: &str) -> client_api::Client { + let new_user_sign_in_link = { + let admin_client = admin_user_client().await; + admin_client + .generate_sign_in_action_link(user_email) + .await + .unwrap() + }; + + let client = localhost_client(); + let appflowy_sign_in_url = client + .extract_sign_in_url(&new_user_sign_in_link) + .await + .unwrap(); + client + .sign_in_with_url(&appflowy_sign_in_url) + .await + .unwrap(); + + client +} + pub fn localhost_gotrue_client() -> gotrue::api::Client { let reqwest_client = reqwest::Client::new(); gotrue::api::Client::new(reqwest_client, &LOCALHOST_GOTRUE) diff --git a/libs/shared-entity/src/dto/workspace_dto.rs b/libs/shared-entity/src/dto/workspace_dto.rs index 7f61f75f..23b4ab7d 100644 --- a/libs/shared-entity/src/dto/workspace_dto.rs +++ b/libs/shared-entity/src/dto/workspace_dto.rs @@ -36,7 +36,7 @@ pub struct CreateWorkspaceMember { pub role: AFRole, } -#[derive(Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize)] pub struct WorkspaceMemberInvitation { pub email: String, pub role: AFRole, diff --git a/src/api/workspace.rs b/src/api/workspace.rs index 6099b3b7..f0b662f1 100644 --- a/src/api/workspace.rs +++ b/src/api/workspace.rs @@ -246,6 +246,8 @@ async fn post_workspace_invite_handler( let invited_members = payload.into_inner(); workspace::ops::invite_workspace_members( &state.pg_pool, + &state.gotrue_admin, + &state.gotrue_client, &user_uuid, &workspace_id, invited_members, diff --git a/src/application.rs b/src/application.rs index ff5b1eae..d5ed63e3 100644 --- a/src/application.rs +++ b/src/application.rs @@ -255,10 +255,7 @@ async fn setup_admin_account( ) -> Result { let admin_email = gotrue_setting.admin_email.as_str(); let password = gotrue_setting.admin_password.as_str(); - let gotrue_admin = GoTrueAdmin { - admin_email: admin_email.to_owned(), - password: admin_email.to_owned().into(), - }; + let gotrue_admin = GoTrueAdmin::new(admin_email.to_owned(), password.to_owned()); let res_resp = gotrue_client.sign_up(admin_email, password, None).await; match res_resp { diff --git a/src/biz/workspace/ops.rs b/src/biz/workspace/ops.rs index 1f8c1ff3..7690d8f6 100644 --- a/src/biz/workspace/ops.rs +++ b/src/biz/workspace/ops.rs @@ -1,4 +1,5 @@ use crate::biz::workspace::access_control::WorkspaceAccessControl; +use crate::state::GoTrueAdmin; use anyhow::Context; use app_error::AppError; use database::collab::upsert_collab_member_with_txn; @@ -17,6 +18,8 @@ use database::workspace::{ use database_entity::dto::{ AFAccessLevel, AFRole, AFWorkspace, AFWorkspaceInvitation, AFWorkspaceInvitationStatus, }; + +use gotrue::params::InviteUserParams; use shared_entity::dto::workspace_dto::{ CreateWorkspaceMember, WorkspaceMemberChangeset, WorkspaceMemberInvitation, }; @@ -26,7 +29,7 @@ use sqlx::{types::uuid, PgPool}; use std::collections::HashMap; use std::ops::DerefMut; use std::sync::Arc; -use tracing::instrument; +use tracing::{info, instrument}; use uuid::Uuid; use workspace_template::document::get_started::GetStartedDocumentTemplate; @@ -159,6 +162,8 @@ pub async fn accept_workspace_invite( #[instrument(level = "debug", skip_all, err)] pub async fn invite_workspace_members( pg_pool: &PgPool, + gotrue_admin: &GoTrueAdmin, + gotrue_client: &gotrue::api::Client, inviter: &Uuid, workspace_id: &Uuid, invitations: Vec, @@ -168,7 +173,37 @@ pub async fn invite_workspace_members( .await .context("Begin transaction to invite workspace members")?; + let admin_token = gotrue_admin.token(gotrue_client).await?; for invitation in invitations { + match gotrue_client + .admin_invite_user( + &admin_token, + &InviteUserParams { + email: invitation.email.clone(), + ..Default::default() + }, + ) + .await + { + Ok(new_user) => { + info!( + "Invited new user: {:?} to workspace: {:?}", + new_user, workspace_id + ); + }, + Err(err) => match err { + app_error::gotrue::GoTrueError::Internal(ref err_serde) => { + match (err_serde.code, err_serde.msg.as_str()) { + (422, "A user with this email address has already been registered") => { + info!("User already exists, skipping invite"); + }, + _ => return Err(AppError::Internal(err.into())), + } + }, + _ => return Err(err.into()), + }, + } + insert_workspace_invitation( &mut txn, workspace_id, diff --git a/src/main.rs b/src/main.rs index e17ba7f4..d1a78602 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,7 +19,7 @@ async fn main() -> anyhow::Result<()> { filters.push(format!("gotrue={}", level)); let conf = get_configuration().map_err(|e| anyhow::anyhow!("Failed to read configuration: {}", e))?; - init_subscriber(&conf.app_env, filters); + // init_subscriber(&conf.app_env, filters); // If current build is debug and the feature "custom_env" is not enabled, load from .env // otherwise, load from .env.without_nginx. diff --git a/tests/workspace/member_crud.rs b/tests/workspace/member_crud.rs index d2c74713..e12da15e 100644 --- a/tests/workspace/member_crud.rs +++ b/tests/workspace/member_crud.rs @@ -1,7 +1,7 @@ use app_error::ErrorCode; -use client_api_test_util::TestClient; +use client_api_test_util::{api_client_with_email, TestClient}; use database_entity::dto::{AFAccessLevel, AFRole, QueryCollabMembers}; -use shared_entity::dto::workspace_dto::CreateWorkspaceMember; +use shared_entity::dto::workspace_dto::WorkspaceMemberInvitation; #[tokio::test] async fn get_workspace_owner_after_sign_up_test() { @@ -73,20 +73,34 @@ async fn add_not_exist_workspace_members() { let c1 = TestClient::new_user_without_ws_conn().await; let workspace_id = c1.workspace_id().await; let email = format!("{}@appflowy.io", uuid::Uuid::new_v4()); - let err = c1 - .api_client - .add_workspace_members( - workspace_id, - vec![CreateWorkspaceMember { - email, + c1.api_client + .invite_workspace_members( + &workspace_id, + vec![WorkspaceMemberInvitation { + email: email.clone(), role: AFRole::Member, }], ) .await - .unwrap_err(); + .unwrap(); - assert_eq!(err.code, ErrorCode::RecordNotFound); + let invited_client = api_client_with_email(&email).await; + let invite_id = invited_client + .list_workspace_invitations(None) + .await + .unwrap() + .first() + .unwrap() + .invite_id; + invited_client + .accept_workspace_invitation(invite_id.to_string().as_str()) + .await + .unwrap(); + + let workspaces = invited_client.get_workspaces().await.unwrap(); + assert_eq!(workspaces.0.len(), 2); } + #[tokio::test] async fn update_workspace_member_role_not_enough_permission() { let c1 = TestClient::new_user_without_ws_conn().await;