From 2fa5ac41acb05b2d7d1335fd253ec418bfd8ae97 Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Fri, 22 Sep 2023 19:04:17 +0100 Subject: [PATCH] refactor: tests for more group routes --- clippy.toml | 2 + crates/core/database/src/events/client.rs | 17 +- .../database/src/models/channels/model.rs | 22 ++- .../core/database/src/models/users/model.rs | 168 ++++++++++++++++ .../src/models/users/ops/reference.rs | 46 ++++- crates/core/database/src/util/reference.rs | 7 +- crates/core/models/src/v0/channels.rs | 13 ++ crates/delta/src/routes/bots/delete.rs | 2 +- crates/delta/src/routes/bots/invite.rs | 4 +- .../src/routes/channels/group_add_member.rs | 184 ++++++++++++++++-- .../delta/src/routes/channels/group_create.rs | 32 +-- .../routes/channels/group_remove_member.rs | 147 ++++++++++++++ crates/delta/src/util/test.rs | 32 ++- 13 files changed, 607 insertions(+), 69 deletions(-) diff --git a/clippy.toml b/clippy.toml index f0c8ae98..a3563b4d 100644 --- a/clippy.toml +++ b/clippy.toml @@ -2,6 +2,8 @@ disallowed-methods = [ # Shouldn't need to access these directly "revolt_database::models::bots::model::Bot::remove_field", "revolt_database::models::messages::model::Message::attach_sendable_embed", + "revolt_database::models::users::model::User::set_relationship", + "revolt_database::models::users::model::User::apply_relationship", # Prefer to use Object::create() "revolt_database::models::bots::ops::AbstractBots::insert_bot", diff --git a/crates/core/database/src/events/client.rs b/crates/core/database/src/events/client.rs index 3f2e812c..b0b67aae 100644 --- a/crates/core/database/src/events/client.rs +++ b/crates/core/database/src/events/client.rs @@ -4,8 +4,8 @@ use serde::{Deserialize, Serialize}; use revolt_models::v0::{ AppendMessage, Channel, Emoji, FieldsChannel, FieldsMember, FieldsRole, FieldsServer, FieldsUser, FieldsWebhook, MemberCompositeKey, Message, PartialChannel, PartialMember, - PartialMessage, PartialRole, PartialServer, PartialUser, PartialWebhook, Server, UserSettings, - Webhook, + PartialMessage, PartialRole, PartialServer, PartialUser, PartialWebhook, Server, User, + UserSettings, Webhook, }; use revolt_result::Error; @@ -154,17 +154,12 @@ pub enum EventV1 { event_id: Option, }, - /*/// Relationship with another user changed - UserRelationship { - id: String, - user: User, - // ! this field can be deprecated - status: RelationshipStatus, - },*/ + /// Relationship with another user changed + UserRelationship { id: String, user: User }, /// Settings updated remotely UserSettingsUpdate { id: String, update: UserSettings }, - /*/// User has been platform banned or deleted their account + /// User has been platform banned or deleted their account /// /// Clients should remove the following associated data: /// - Messages @@ -173,7 +168,7 @@ pub enum EventV1 { /// - Server Memberships /// /// User flags are specified to explain why a wipe is occurring though not all reasons will necessarily ever appear. - UserPlatformWipe { user_id: String, flags: i32 }, */ + UserPlatformWipe { user_id: String, flags: i32 }, /// New emoji EmojiCreate(Emoji), diff --git a/crates/core/database/src/models/channels/model.rs b/crates/core/database/src/models/channels/model.rs index ad92cb2e..9e1f6b7b 100644 --- a/crates/core/database/src/models/channels/model.rs +++ b/crates/core/database/src/models/channels/model.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; +use revolt_config::config; use revolt_models::v0::{self, MessageAuthor}; use revolt_permissions::OverrideField; use revolt_result::Result; @@ -191,9 +192,18 @@ impl Channel { /// Create a group pub async fn create_group( db: &Database, - data: v0::DataCreateGroup, + mut data: v0::DataCreateGroup, owner_id: String, ) -> Result { + data.users.insert(owner_id.to_string()); + + let config = config().await; + if data.users.len() > config.features.limits.default.group_size { + return Err(create_error!(GroupTooLarge { + max: config.features.limits.default.group_size, + })); + } + let recipients = data.users.into_iter().collect::>(); let channel = Channel::Group { id: ulid::Ulid::new().to_string(), @@ -247,10 +257,6 @@ impl Channel { .p(id.to_string()) .await; - EventV1::ChannelCreate(self.clone().into()) - .private(user.id.to_string()) - .await; - SystemMessage::UserAdded { id: user.id.to_string(), by: by_id.to_string(), @@ -268,6 +274,10 @@ impl Channel { .await .ok(); + EventV1::ChannelCreate(self.clone().into()) + .private(user.id.to_string()) + .await; + Ok(()) } _ => Err(create_error!(InvalidOperation)), @@ -295,7 +305,7 @@ impl Channel { } } - /// Get a reference to this channel's id + /// Clone this channel's id pub fn id(&self) -> String { match self { Channel::DirectMessage { id, .. } diff --git a/crates/core/database/src/models/users/model.rs b/crates/core/database/src/models/users/model.rs index 89a10949..305267b0 100644 --- a/crates/core/database/src/models/users/model.rs +++ b/crates/core/database/src/models/users/model.rs @@ -197,6 +197,13 @@ impl User { RelationshipStatus::None } + pub fn is_friends_with(&self, user_b: &str) -> bool { + matches!( + self.relationship_with(user_b), + RelationshipStatus::Friend | RelationshipStatus::User + ) + } + /// Check whether two users have a mutual connection /// /// This will check if user and user_b share a server or a group. @@ -341,6 +348,167 @@ impl User { } } + /// Set a relationship to another user + pub async fn set_relationship( + &mut self, + db: &Database, + user_b: &User, + status: RelationshipStatus, + ) -> Result<()> { + db.set_relationship(&self.id, &user_b.id, &status).await?; + + if let RelationshipStatus::None | RelationshipStatus::User = status { + if let Some(relations) = &mut self.relations { + relations.retain(|relation| relation.id != user_b.id); + } + } else { + let relation = Relationship { + id: user_b.id.to_string(), + status, + }; + + if let Some(relations) = &mut self.relations { + relations.retain(|relation| relation.id != user_b.id); + relations.push(relation); + } else { + self.relations = Some(vec![relation]); + } + } + + Ok(()) + } + + /// Apply a certain relationship between two users + pub async fn apply_relationship( + &mut self, + db: &Database, + target: &mut User, + local: RelationshipStatus, + remote: RelationshipStatus, + ) -> Result<()> { + target.set_relationship(db, self, remote).await?; + self.set_relationship(db, target, local).await?; + + EventV1::UserRelationship { + id: target.id.clone(), + user: self.clone().into(Some(&*target)).await, + } + .private(target.id.clone()) + .await; + + EventV1::UserRelationship { + id: self.id.clone(), + user: target.clone().into(Some(&*self)).await, + } + .private(self.id.clone()) + .await; + + Ok(()) + } + + /// Add another user as a friend + pub async fn add_friend(&mut self, db: &Database, target: &mut User) -> Result<()> { + match self.relationship_with(&target.id) { + RelationshipStatus::User => Err(create_error!(NoEffect)), + RelationshipStatus::Friend => Err(create_error!(AlreadyFriends)), + RelationshipStatus::Outgoing => Err(create_error!(AlreadySentRequest)), + RelationshipStatus::Blocked => Err(create_error!(Blocked)), + RelationshipStatus::BlockedOther => Err(create_error!(BlockedByOther)), + RelationshipStatus::Incoming => { + self.apply_relationship( + db, + target, + RelationshipStatus::Friend, + RelationshipStatus::Friend, + ) + .await + } + RelationshipStatus::None => { + self.apply_relationship( + db, + target, + RelationshipStatus::Outgoing, + RelationshipStatus::Incoming, + ) + .await + } + } + } + + /// Remove another user as a friend + pub async fn remove_friend(&mut self, db: &Database, target: &mut User) -> Result<()> { + match self.relationship_with(&target.id) { + RelationshipStatus::Friend + | RelationshipStatus::Outgoing + | RelationshipStatus::Incoming => { + self.apply_relationship( + db, + target, + RelationshipStatus::None, + RelationshipStatus::None, + ) + .await + } + _ => Err(create_error!(NoEffect)), + } + } + + /// Block another user + pub async fn block_user(&mut self, db: &Database, target: &mut User) -> Result<()> { + match self.relationship_with(&target.id) { + RelationshipStatus::User | RelationshipStatus::Blocked => Err(create_error!(NoEffect)), + RelationshipStatus::BlockedOther => { + self.apply_relationship( + db, + target, + RelationshipStatus::Blocked, + RelationshipStatus::Blocked, + ) + .await + } + RelationshipStatus::None + | RelationshipStatus::Friend + | RelationshipStatus::Incoming + | RelationshipStatus::Outgoing => { + self.apply_relationship( + db, + target, + RelationshipStatus::Blocked, + RelationshipStatus::BlockedOther, + ) + .await + } + } + } + + /// Unblock another user + pub async fn unblock_user(&mut self, db: &Database, target: &mut User) -> Result<()> { + match self.relationship_with(&target.id) { + RelationshipStatus::Blocked => match target.relationship_with(&self.id) { + RelationshipStatus::Blocked => { + self.apply_relationship( + db, + target, + RelationshipStatus::BlockedOther, + RelationshipStatus::Blocked, + ) + .await + } + RelationshipStatus::BlockedOther => { + self.apply_relationship( + db, + target, + RelationshipStatus::None, + RelationshipStatus::None, + ) + .await + } + _ => Err(create_error!(InternalError)), + }, + _ => Err(create_error!(NoEffect)), + } + } + /// Update user data pub async fn update<'a>( &mut self, diff --git a/crates/core/database/src/models/users/ops/reference.rs b/crates/core/database/src/models/users/ops/reference.rs index c557bea5..6b8dce31 100644 --- a/crates/core/database/src/models/users/ops/reference.rs +++ b/crates/core/database/src/models/users/ops/reference.rs @@ -1,7 +1,7 @@ use revolt_result::Result; -use crate::ReferenceDb; use crate::{FieldsUser, PartialUser, RelationshipStatus, User}; +use crate::{ReferenceDb, Relationship}; use super::AbstractUsers; @@ -106,19 +106,49 @@ impl AbstractUsers for ReferenceDb { /// Set relationship with another user /// - /// This should use pull_relationship if relationship is None. + /// This should use pull_relationship if relationship is None or User. async fn set_relationship( &self, - _user_id: &str, - _target_id: &str, - _relationship: &RelationshipStatus, + user_id: &str, + target_id: &str, + relationship: &RelationshipStatus, ) -> Result<()> { - todo!() + if let RelationshipStatus::User | RelationshipStatus::None = &relationship { + self.pull_relationship(user_id, target_id).await + } else { + let mut users = self.users.lock().await; + let user = users + .get_mut(user_id) + .ok_or_else(|| create_error!(NotFound))?; + + let relation = Relationship { + id: target_id.to_string(), + status: relationship.clone(), + }; + + if let Some(relations) = &mut user.relations { + relations.retain(|relation| relation.id != target_id); + relations.push(relation); + } else { + user.relations = Some(vec![relation]); + } + + Ok(()) + } } /// Remove relationship with another user - async fn pull_relationship(&self, _user_id: &str, _target_id: &str) -> Result<()> { - todo!() + async fn pull_relationship(&self, user_id: &str, target_id: &str) -> Result<()> { + let mut users = self.users.lock().await; + let user = users + .get_mut(user_id) + .ok_or_else(|| create_error!(NotFound))?; + + if let Some(relations) = &mut user.relations { + relations.retain(|relation| relation.id != target_id); + } + + Ok(()) } /// Delete a user by their id diff --git a/crates/core/database/src/util/reference.rs b/crates/core/database/src/util/reference.rs index 7befa54d..033dd0e9 100644 --- a/crates/core/database/src/util/reference.rs +++ b/crates/core/database/src/util/reference.rs @@ -7,7 +7,7 @@ use schemars::{ JsonSchema, }; -use crate::{Bot, Channel, Database, Emoji, Message, Webhook}; +use crate::{Bot, Channel, Database, Emoji, Message, User, Webhook}; /// Reference to some object in the database #[derive(Serialize, Deserialize)] @@ -42,6 +42,11 @@ impl Reference { db.fetch_message(&self.id).await } + /// Fetch user from Ref + pub async fn as_user(&self, db: &Database) -> Result { + db.fetch_user(&self.id).await + } + /// Fetch webhook from Ref pub async fn as_webhook(&self, db: &Database) -> Result { db.fetch_webhook(&self.id).await diff --git a/crates/core/models/src/v0/channels.rs b/crates/core/models/src/v0/channels.rs index c039802a..4e5f55b0 100644 --- a/crates/core/models/src/v0/channels.rs +++ b/crates/core/models/src/v0/channels.rs @@ -231,3 +231,16 @@ auto_derived!( pub nsfw: Option, } ); + +impl Channel { + /// Get a reference to this channel's id + pub fn id(&self) -> &str { + match self { + Channel::DirectMessage { id, .. } + | Channel::Group { id, .. } + | Channel::SavedMessages { id, .. } + | Channel::TextChannel { id, .. } + | Channel::VoiceChannel { id, .. } => id, + } + } +} diff --git a/crates/delta/src/routes/bots/delete.rs b/crates/delta/src/routes/bots/delete.rs index b9f4701a..6b421b67 100644 --- a/crates/delta/src/routes/bots/delete.rs +++ b/crates/delta/src/routes/bots/delete.rs @@ -48,7 +48,7 @@ mod test { drop(response); let event = harness - .wait_for_event(|event| match event { + .wait_for_event(&bot.id, |event| match event { EventV1::UserUpdate { id, .. } => id == &bot.id, _ => false, }) diff --git a/crates/delta/src/routes/bots/invite.rs b/crates/delta/src/routes/bots/invite.rs index 40af6eda..eada9682 100644 --- a/crates/delta/src/routes/bots/invite.rs +++ b/crates/delta/src/routes/bots/invite.rs @@ -102,7 +102,7 @@ mod test { drop(response); let event = harness - .wait_for_event(|event| match event { + .wait_for_event(&group.id(), |event| match event { EventV1::ChannelGroupJoin { id, .. } => id == &group.id(), _ => false, }) @@ -164,7 +164,7 @@ mod test { drop(response); let event = harness - .wait_for_event(|event| match event { + .wait_for_event(&server.id, |event| match event { EventV1::ServerMemberJoin { id, .. } => id == &server.id, _ => false, }) diff --git a/crates/delta/src/routes/channels/group_add_member.rs b/crates/delta/src/routes/channels/group_add_member.rs index b1dc9c03..47989314 100644 --- a/crates/delta/src/routes/channels/group_add_member.rs +++ b/crates/delta/src/routes/channels/group_add_member.rs @@ -1,40 +1,184 @@ -use revolt_quark::{ - get_relationship, - models::{user::RelationshipStatus, Channel, User}, - perms, Db, EmptyResponse, Error, Permission, Ref, Result, +use revolt_database::{ + util::{permissions::DatabasePermissionQuery, reference::Reference}, + Channel, Database, User, }; +use revolt_permissions::{calculate_channel_permissions, ChannelPermission}; +use revolt_result::{create_error, Result}; + +use rocket::State; +use rocket_empty::EmptyResponse; /// # Add Member to Group /// /// Adds another user to the group. #[openapi(tag = "Groups")] -#[put("//recipients/")] -pub async fn req(db: &Db, user: User, target: Ref, member: Ref) -> Result { +#[put("//recipients/")] +pub async fn req( + db: &State, + user: User, + group_id: Reference, + member_id: Reference, +) -> Result { if user.bot.is_some() { - return Err(Error::IsBot); + return Err(create_error!(IsBot)); } - let mut channel = target.as_channel(db).await?; - perms(&user) - .channel(&channel) - .throw_permission_and_view_channel(db, Permission::InviteOthers) - .await?; + let mut channel = group_id.as_channel(db).await?; + let mut query = DatabasePermissionQuery::new(db, &user).channel(&channel); + calculate_channel_permissions(&mut query) + .await + .throw_if_lacking_channel_permission(ChannelPermission::InviteOthers)?; match &channel { Channel::Group { .. } => { - let member = member.as_user(db).await?; - if !matches!( - get_relationship(&user, &member.id), - RelationshipStatus::Friend - ) { - return Err(Error::NotFriends); + // FIXME: use permissions here? + // interesting if users could block new group invites + let member = member_id.as_user(db).await?; + if !user.is_friends_with(&member.id) { + return Err(create_error!(NotFriends)); } channel - .add_user_to_group(db, &member.id, &user.id) + .add_user_to_group(db, &member, &user.id) .await .map(|_| EmptyResponse) } - _ => Err(Error::InvalidOperation), + _ => Err(create_error!(InvalidOperation)), + } +} + +#[cfg(test)] +mod test { + use crate::{rocket, util::test::TestHarness}; + use revolt_database::{events::client::EventV1, Channel, RelationshipStatus}; + use revolt_models::v0; + use rocket::http::{Header, Status}; + + #[rocket::async_test] + async fn success_add_member() { + let mut harness = TestHarness::new().await; + let (_, session, mut user) = harness.new_user().await; + let (_, _, mut other_user) = harness.new_user().await; + + #[allow(clippy::disallowed_methods)] + user.apply_relationship( + &harness.db, + &mut other_user, + RelationshipStatus::Friend, + RelationshipStatus::Friend, + ) + .await + .unwrap(); + + let group = Channel::create_group( + &harness.db, + v0::DataCreateGroup { + name: TestHarness::rand_string(), + ..Default::default() + }, + user.id.to_string(), + ) + .await + .unwrap(); + + let response = harness + .client + .put(format!( + "/channels/{}/recipients/{}", + group.id(), + other_user.id + )) + .header(Header::new("x-session-token", session.token.to_string())) + .dispatch() + .await; + + assert_eq!(response.status(), Status::NoContent); + drop(response); + + harness + .wait_for_event(&format!("{}!", other_user.id), |event| match event { + EventV1::ChannelCreate(channel) => channel.id() == group.id(), + _ => false, + }) + .await; + + let event = harness + .wait_for_event(&group.id(), |event| match event { + EventV1::ChannelGroupJoin { id, .. } => id == &group.id(), + _ => false, + }) + .await; + + match event { + EventV1::ChannelGroupJoin { user, .. } => assert_eq!(user, other_user.id), + _ => unreachable!(), + }; + + let message = harness.wait_for_message(&group.id()).await; + + assert_eq!( + message.system, + Some(v0::SystemMessage::UserAdded { + id: other_user.id.to_string(), + by: user.id.to_string() + }) + ); + } + + #[rocket::async_test] + async fn fail_add_non_friend() { + let harness = TestHarness::new().await; + let (_, session, user) = harness.new_user().await; + let (_, _, other_user) = harness.new_user().await; + + let group = Channel::create_group( + &harness.db, + v0::DataCreateGroup { + name: TestHarness::rand_string(), + ..Default::default() + }, + user.id.to_string(), + ) + .await + .unwrap(); + + let response = harness + .client + .put(format!( + "/channels/{}/recipients/{}", + group.id(), + other_user.id + )) + .header(Header::new("x-session-token", session.token.to_string())) + .dispatch() + .await; + + assert_eq!(response.status(), Status::Forbidden); + } + + #[rocket::async_test] + async fn fail_add_already_in_group() { + let harness = TestHarness::new().await; + let (_, session, user) = harness.new_user().await; + + let group = Channel::create_group( + &harness.db, + v0::DataCreateGroup { + name: TestHarness::rand_string(), + ..Default::default() + }, + user.id.to_string(), + ) + .await + .unwrap(); + + let response = harness + .client + .put(format!("/channels/{}/recipients/{}", group.id(), user.id)) + .header(Header::new("x-session-token", session.token.to_string())) + .dispatch() + .await; + + assert_eq!(response.status(), Status::Conflict); } } diff --git a/crates/delta/src/routes/channels/group_create.rs b/crates/delta/src/routes/channels/group_create.rs index 62d0b527..bf6387c5 100644 --- a/crates/delta/src/routes/channels/group_create.rs +++ b/crates/delta/src/routes/channels/group_create.rs @@ -1,4 +1,3 @@ -use revolt_config::config; use revolt_database::{Channel, Database, RelationshipStatus, User}; use revolt_models::v0; use revolt_result::{create_error, Result}; @@ -17,26 +16,17 @@ pub async fn create_group( user: User, data: Json, ) -> Result> { - let config = config().await; if user.bot.is_some() { return Err(create_error!(IsBot)); } - let mut data = data.into_inner(); + let data = data.into_inner(); data.validate().map_err(|error| { create_error!(FailedValidation { error: error.to_string() }) })?; - data.users.insert(user.id.to_string()); - - if data.users.len() > config.features.limits.default.group_size { - return Err(create_error!(GroupTooLarge { - max: config.features.limits.default.group_size, - })); - } - for target in &data.users { match user.relationship_with(target) { RelationshipStatus::Friend | RelationshipStatus::User => {} @@ -52,12 +42,13 @@ pub async fn create_group( #[cfg(test)] mod test { use crate::{rocket, util::test::TestHarness}; + use revolt_database::events::client::EventV1; use revolt_models::v0; use rocket::http::{ContentType, Header, Status}; #[rocket::async_test] async fn create_group() { - let harness = TestHarness::new().await; + let mut harness = TestHarness::new().await; let (_, session, user) = harness.new_user().await; let response = harness @@ -88,7 +79,22 @@ mod test { assert_eq!(recipients.len(), 1); assert!(harness.db.fetch_channel(&id).await.is_ok()); - // TODO: does not check for events + let event = harness + .wait_for_event(&format!("{}!", user.id), |event| match event { + EventV1::ChannelCreate(channel) => channel.id() == id, + _ => false, + }) + .await; + + match event { + EventV1::ChannelCreate(v0::Channel::Group { + owner: channel_owner, + .. + }) => { + assert_eq!(owner, channel_owner); + } + _ => unreachable!(), + } } _ => unreachable!(), } diff --git a/crates/delta/src/routes/channels/group_remove_member.rs b/crates/delta/src/routes/channels/group_remove_member.rs index 5847e713..ce1cf0d1 100644 --- a/crates/delta/src/routes/channels/group_remove_member.rs +++ b/crates/delta/src/routes/channels/group_remove_member.rs @@ -40,3 +40,150 @@ pub async fn req(db: &Db, user: User, target: Ref, member: Ref) -> Result Err(Error::InvalidOperation), } } + +#[cfg(test)] +mod test { + use crate::{rocket, util::test::TestHarness}; + use revolt_database::{events::client::EventV1, Channel, RelationshipStatus}; + use revolt_models::v0; + use rocket::http::{Header, Status}; + + #[rocket::async_test] + async fn success_remove_member() { + let mut harness = TestHarness::new().await; + let (_, session, mut user) = harness.new_user().await; + let (_, _, mut other_user) = harness.new_user().await; + + #[allow(clippy::disallowed_methods)] + user.apply_relationship( + &harness.db, + &mut other_user, + RelationshipStatus::Friend, + RelationshipStatus::Friend, + ) + .await + .unwrap(); + + let group = Channel::create_group( + &harness.db, + v0::DataCreateGroup { + name: TestHarness::rand_string(), + ..Default::default() + }, + user.id.to_string(), + ) + .await + .unwrap(); + + let response = harness + .client + .put(format!( + "/channels/{}/recipients/{}", + group.id(), + other_user.id + )) + .header(Header::new("x-session-token", session.token.to_string())) + .dispatch() + .await; + + assert_eq!(response.status(), Status::NoContent); + drop(response); + + harness + .wait_for_event(&format!("{}!", other_user.id), |event| match event { + EventV1::ChannelCreate(channel) => channel.id() == group.id(), + _ => false, + }) + .await; + + let event = harness + .wait_for_event(&group.id(), |event| match event { + EventV1::ChannelGroupJoin { id, .. } => id == &group.id(), + _ => false, + }) + .await; + + match event { + EventV1::ChannelGroupJoin { user, .. } => assert_eq!(user, other_user.id), + _ => unreachable!(), + }; + + let message = harness.wait_for_message(&group.id()).await; + + assert_eq!( + message.system, + Some(v0::SystemMessage::UserAdded { + id: other_user.id.to_string(), + by: user.id.to_string() + }) + ); + } + + #[rocket::async_test] + async fn fail_not_in_group() { + let harness = TestHarness::new().await; + let (_, session, user) = harness.new_user().await; + let (_, _, other_user) = harness.new_user().await; + + let group = Channel::create_group( + &harness.db, + v0::DataCreateGroup { + name: TestHarness::rand_string(), + ..Default::default() + }, + user.id.to_string(), + ) + .await + .unwrap(); + + let response = harness + .client + .delete(format!( + "/channels/{}/recipients/{}", + group.id(), + other_user.id + )) + .header(Header::new("x-session-token", session.token.to_string())) + .dispatch() + .await; + + dbg!(response.into_string().await); + // assert_eq!(response.status(), Status::NotFound); + } + + #[rocket::async_test] + async fn fail_not_group_owner() { + let harness = TestHarness::new().await; + let (_, _, user) = harness.new_user().await; + let (_, session, other_user) = harness.new_user().await; + let (_, _, user_to_be_kicked) = harness.new_user().await; + + let group = Channel::create_group( + &harness.db, + v0::DataCreateGroup { + name: TestHarness::rand_string(), + users: vec![&other_user.id, &user_to_be_kicked.id] + .into_iter() + .cloned() + .collect(), + ..Default::default() + }, + user.id.to_string(), + ) + .await + .unwrap(); + + let response = harness + .client + .delete(format!( + "/channels/{}/recipients/{}", + group.id(), + user_to_be_kicked.id + )) + .header(Header::new("x-session-token", session.token.to_string())) + .dispatch() + .await; + + assert_eq!(response.status(), Status::Forbidden); + } +} diff --git a/crates/delta/src/util/test.rs b/crates/delta/src/util/test.rs index 136c0295..250b8537 100644 --- a/crates/delta/src/util/test.rs +++ b/crates/delta/src/util/test.rs @@ -2,6 +2,7 @@ use futures::StreamExt; use rand::Rng; use redis_kiss::redis::aio::PubSub; use revolt_database::{events::client::EventV1, Database, User}; +use revolt_models::v0; use revolt_quark::authifier::{ models::{Account, Session}, Authifier, @@ -13,7 +14,7 @@ pub struct TestHarness { authifier: Authifier, pub db: Database, sub: PubSub, - event_buffer: Vec, + event_buffer: Vec<(String, EventV1)>, } impl TestHarness { @@ -87,12 +88,12 @@ impl TestHarness { (account, session, user) } - pub async fn wait_for_event(&mut self, predicate: F) -> EventV1 + pub async fn wait_for_event(&mut self, topic: &str, predicate: F) -> EventV1 where F: Fn(&EventV1) -> bool, { - for event in &self.event_buffer { - if predicate(event) { + for (msg_topic, event) in &self.event_buffer { + if topic == msg_topic && predicate(event) { // does not remove from buffer return event.clone(); } @@ -100,13 +101,15 @@ impl TestHarness { let mut stream = self.sub.on_message(); while let Some(item) = stream.next().await { - let payload: EventV1 = redis_kiss::decode_payload(&item.unwrap()).unwrap(); + let item = item.unwrap(); + let msg_topic = item.get_channel_name(); + let payload: EventV1 = redis_kiss::decode_payload(&item).unwrap(); - if predicate(&payload) { + if topic == msg_topic && predicate(&payload) { return payload; } - self.event_buffer.push(payload); + self.event_buffer.push((msg_topic.to_string(), payload)); } // WARNING: if predicate is never satisfied, this will never return @@ -114,4 +117,19 @@ impl TestHarness { unreachable!() } + + pub async fn wait_for_message(&mut self, channel_id: &str) -> v0::Message { + dbg!(&self.event_buffer); + + match self + .wait_for_event(channel_id, |event| match event { + EventV1::Message(v0::Message { channel, .. }) => channel == channel_id, + _ => false, + }) + .await + { + EventV1::Message(message) => message, + _ => unreachable!(), + } + } }