refactor: consolidate 11 social use cases → SocialCmd/SocialQry enum dispatch
-280 net lines. 11 one-file use cases replaced by execute_command/ execute_query with enum dispatch. Added FollowRejected event (reject was silently dropping). Command→event mapping now explicit in one match.
This commit is contained in:
@@ -1,23 +0,0 @@
|
||||
use domain::{errors::DomainError, events::DomainEvent, value_objects::UserId};
|
||||
|
||||
use super::{commands::AcceptFollowCommand, deps::SocialCommandDeps};
|
||||
|
||||
pub async fn execute(
|
||||
deps: &SocialCommandDeps,
|
||||
cmd: AcceptFollowCommand,
|
||||
) -> Result<(), DomainError> {
|
||||
let owner = UserId::from_uuid(cmd.owner_id);
|
||||
deps.social_command
|
||||
.accept_follow(&owner, &cmd.requester)
|
||||
.await?;
|
||||
deps.event_publisher
|
||||
.publish(&DomainEvent::FollowAccepted {
|
||||
owner,
|
||||
requester: cmd.requester,
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests/accept.rs"]
|
||||
mod tests;
|
||||
@@ -1,18 +0,0 @@
|
||||
use domain::{errors::DomainError, events::DomainEvent, value_objects::UserId};
|
||||
|
||||
use super::{commands::BlockCommand, deps::SocialCommandDeps};
|
||||
|
||||
pub async fn execute(deps: &SocialCommandDeps, cmd: BlockCommand) -> Result<(), DomainError> {
|
||||
let blocker = UserId::from_uuid(cmd.blocker_id);
|
||||
deps.social_command.block(&blocker, &cmd.target).await?;
|
||||
deps.event_publisher
|
||||
.publish(&DomainEvent::ActorBlocked {
|
||||
blocker,
|
||||
target: cmd.target,
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests/block.rs"]
|
||||
mod tests;
|
||||
@@ -1,37 +1,33 @@
|
||||
use domain::value_objects::{FollowTarget, SocialIdentity};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub struct FollowCommand {
|
||||
pub follower_id: Uuid,
|
||||
pub target: FollowTarget,
|
||||
}
|
||||
|
||||
pub struct UnfollowCommand {
|
||||
pub follower_id: Uuid,
|
||||
pub target: SocialIdentity,
|
||||
}
|
||||
|
||||
pub struct AcceptFollowCommand {
|
||||
pub owner_id: Uuid,
|
||||
pub requester: SocialIdentity,
|
||||
}
|
||||
|
||||
pub struct RejectFollowCommand {
|
||||
pub owner_id: Uuid,
|
||||
pub requester: SocialIdentity,
|
||||
}
|
||||
|
||||
pub struct RemoveFollowerCommand {
|
||||
pub owner_id: Uuid,
|
||||
pub follower: SocialIdentity,
|
||||
}
|
||||
|
||||
pub struct BlockCommand {
|
||||
pub blocker_id: Uuid,
|
||||
pub target: SocialIdentity,
|
||||
}
|
||||
|
||||
pub struct UnblockCommand {
|
||||
pub blocker_id: Uuid,
|
||||
pub target: SocialIdentity,
|
||||
pub enum SocialCmd {
|
||||
Follow {
|
||||
follower_id: Uuid,
|
||||
target: FollowTarget,
|
||||
},
|
||||
Unfollow {
|
||||
follower_id: Uuid,
|
||||
target: SocialIdentity,
|
||||
},
|
||||
AcceptFollow {
|
||||
owner_id: Uuid,
|
||||
requester: SocialIdentity,
|
||||
},
|
||||
RejectFollow {
|
||||
owner_id: Uuid,
|
||||
requester: SocialIdentity,
|
||||
},
|
||||
RemoveFollower {
|
||||
owner_id: Uuid,
|
||||
follower: SocialIdentity,
|
||||
},
|
||||
Block {
|
||||
blocker_id: Uuid,
|
||||
target: SocialIdentity,
|
||||
},
|
||||
Unblock {
|
||||
blocker_id: Uuid,
|
||||
target: SocialIdentity,
|
||||
},
|
||||
}
|
||||
|
||||
101
crates/application/src/social/execute.rs
Normal file
101
crates/application/src/social/execute.rs
Normal file
@@ -0,0 +1,101 @@
|
||||
use domain::{
|
||||
errors::DomainError,
|
||||
events::DomainEvent,
|
||||
value_objects::{SocialActor, UserId},
|
||||
};
|
||||
|
||||
use super::{
|
||||
commands::SocialCmd,
|
||||
deps::{SocialCommandDeps, SocialQueryDeps},
|
||||
queries::SocialQry,
|
||||
};
|
||||
|
||||
pub async fn execute_command(
|
||||
deps: &SocialCommandDeps,
|
||||
cmd: SocialCmd,
|
||||
) -> Result<(), DomainError> {
|
||||
let event = match cmd {
|
||||
SocialCmd::Follow {
|
||||
follower_id,
|
||||
target,
|
||||
} => {
|
||||
let follower = UserId::from_uuid(follower_id);
|
||||
deps.social_command.follow(&follower, &target).await?;
|
||||
DomainEvent::FollowRequested { follower, target }
|
||||
}
|
||||
SocialCmd::Unfollow {
|
||||
follower_id,
|
||||
target,
|
||||
} => {
|
||||
let follower = UserId::from_uuid(follower_id);
|
||||
deps.social_command.unfollow(&follower, &target).await?;
|
||||
DomainEvent::Unfollowed { follower, target }
|
||||
}
|
||||
SocialCmd::AcceptFollow {
|
||||
owner_id,
|
||||
requester,
|
||||
} => {
|
||||
let owner = UserId::from_uuid(owner_id);
|
||||
deps.social_command
|
||||
.accept_follow(&owner, &requester)
|
||||
.await?;
|
||||
DomainEvent::FollowAccepted { owner, requester }
|
||||
}
|
||||
SocialCmd::RejectFollow {
|
||||
owner_id,
|
||||
requester,
|
||||
} => {
|
||||
let owner = UserId::from_uuid(owner_id);
|
||||
deps.social_command
|
||||
.reject_follow(&owner, &requester)
|
||||
.await?;
|
||||
DomainEvent::FollowRejected { owner, requester }
|
||||
}
|
||||
SocialCmd::RemoveFollower { owner_id, follower } => {
|
||||
let owner = UserId::from_uuid(owner_id);
|
||||
deps.social_command
|
||||
.remove_follower(&owner, &follower)
|
||||
.await?;
|
||||
DomainEvent::FollowerRemoved { owner, follower }
|
||||
}
|
||||
SocialCmd::Block {
|
||||
blocker_id,
|
||||
target,
|
||||
} => {
|
||||
let blocker = UserId::from_uuid(blocker_id);
|
||||
deps.social_command.block(&blocker, &target).await?;
|
||||
DomainEvent::ActorBlocked { blocker, target }
|
||||
}
|
||||
SocialCmd::Unblock {
|
||||
blocker_id,
|
||||
target,
|
||||
} => {
|
||||
let blocker = UserId::from_uuid(blocker_id);
|
||||
deps.social_command.unblock(&blocker, &target).await?;
|
||||
DomainEvent::ActorUnblocked { blocker, target }
|
||||
}
|
||||
};
|
||||
deps.event_publisher.publish(&event).await
|
||||
}
|
||||
|
||||
pub async fn execute_query(
|
||||
deps: &SocialQueryDeps,
|
||||
query: SocialQry,
|
||||
) -> Result<Vec<SocialActor>, DomainError> {
|
||||
let user_id = match &query {
|
||||
SocialQry::GetFollowing { user_id }
|
||||
| SocialQry::GetFollowers { user_id }
|
||||
| SocialQry::GetPending { user_id }
|
||||
| SocialQry::GetBlocked { user_id } => UserId::from_uuid(*user_id),
|
||||
};
|
||||
match query {
|
||||
SocialQry::GetFollowing { .. } => deps.social_query.get_following(&user_id).await,
|
||||
SocialQry::GetFollowers { .. } => deps.social_query.get_followers(&user_id).await,
|
||||
SocialQry::GetPending { .. } => deps.social_query.get_pending_followers(&user_id).await,
|
||||
SocialQry::GetBlocked { .. } => deps.social_query.get_blocked(&user_id).await,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests/execute.rs"]
|
||||
mod tests;
|
||||
@@ -1,18 +0,0 @@
|
||||
use domain::{errors::DomainError, events::DomainEvent, value_objects::UserId};
|
||||
|
||||
use super::{commands::FollowCommand, deps::SocialCommandDeps};
|
||||
|
||||
pub async fn execute(deps: &SocialCommandDeps, cmd: FollowCommand) -> Result<(), DomainError> {
|
||||
let follower = UserId::from_uuid(cmd.follower_id);
|
||||
deps.social_command.follow(&follower, &cmd.target).await?;
|
||||
deps.event_publisher
|
||||
.publish(&DomainEvent::FollowRequested {
|
||||
follower,
|
||||
target: cmd.target,
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests/follow.rs"]
|
||||
mod tests;
|
||||
@@ -1,14 +0,0 @@
|
||||
use domain::{
|
||||
errors::DomainError,
|
||||
value_objects::{SocialActor, UserId},
|
||||
};
|
||||
|
||||
use super::{deps::SocialQueryDeps, queries::GetBlockedQuery};
|
||||
|
||||
pub async fn execute(
|
||||
deps: &SocialQueryDeps,
|
||||
query: GetBlockedQuery,
|
||||
) -> Result<Vec<SocialActor>, DomainError> {
|
||||
let user_id = UserId::from_uuid(query.user_id);
|
||||
deps.social_query.get_blocked(&user_id).await
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
use domain::{
|
||||
errors::DomainError,
|
||||
value_objects::{SocialActor, UserId},
|
||||
};
|
||||
|
||||
use super::{deps::SocialQueryDeps, queries::GetFollowersQuery};
|
||||
|
||||
pub async fn execute(
|
||||
deps: &SocialQueryDeps,
|
||||
query: GetFollowersQuery,
|
||||
) -> Result<Vec<SocialActor>, DomainError> {
|
||||
let user_id = UserId::from_uuid(query.user_id);
|
||||
deps.social_query.get_followers(&user_id).await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests/get_followers.rs"]
|
||||
mod tests;
|
||||
@@ -1,18 +0,0 @@
|
||||
use domain::{
|
||||
errors::DomainError,
|
||||
value_objects::{SocialActor, UserId},
|
||||
};
|
||||
|
||||
use super::{deps::SocialQueryDeps, queries::GetFollowingQuery};
|
||||
|
||||
pub async fn execute(
|
||||
deps: &SocialQueryDeps,
|
||||
query: GetFollowingQuery,
|
||||
) -> Result<Vec<SocialActor>, DomainError> {
|
||||
let user_id = UserId::from_uuid(query.user_id);
|
||||
deps.social_query.get_following(&user_id).await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests/get_following.rs"]
|
||||
mod tests;
|
||||
@@ -1,18 +0,0 @@
|
||||
use domain::{
|
||||
errors::DomainError,
|
||||
value_objects::{SocialActor, UserId},
|
||||
};
|
||||
|
||||
use super::{deps::SocialQueryDeps, queries::GetPendingFollowersQuery};
|
||||
|
||||
pub async fn execute(
|
||||
deps: &SocialQueryDeps,
|
||||
query: GetPendingFollowersQuery,
|
||||
) -> Result<Vec<SocialActor>, DomainError> {
|
||||
let user_id = UserId::from_uuid(query.user_id);
|
||||
deps.social_query.get_pending_followers(&user_id).await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests/get_pending.rs"]
|
||||
mod tests;
|
||||
@@ -1,15 +1,4 @@
|
||||
pub mod commands;
|
||||
pub mod deps;
|
||||
pub mod execute;
|
||||
pub mod queries;
|
||||
|
||||
pub mod accept;
|
||||
pub mod block;
|
||||
pub mod follow;
|
||||
pub mod get_blocked;
|
||||
pub mod get_followers;
|
||||
pub mod get_following;
|
||||
pub mod get_pending;
|
||||
pub mod reject;
|
||||
pub mod remove_follower;
|
||||
pub mod unblock;
|
||||
pub mod unfollow;
|
||||
|
||||
@@ -1,17 +1,8 @@
|
||||
use uuid::Uuid;
|
||||
|
||||
pub struct GetFollowingQuery {
|
||||
pub user_id: Uuid,
|
||||
}
|
||||
|
||||
pub struct GetFollowersQuery {
|
||||
pub user_id: Uuid,
|
||||
}
|
||||
|
||||
pub struct GetPendingFollowersQuery {
|
||||
pub user_id: Uuid,
|
||||
}
|
||||
|
||||
pub struct GetBlockedQuery {
|
||||
pub user_id: Uuid,
|
||||
pub enum SocialQry {
|
||||
GetFollowing { user_id: Uuid },
|
||||
GetFollowers { user_id: Uuid },
|
||||
GetPending { user_id: Uuid },
|
||||
GetBlocked { user_id: Uuid },
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
use domain::{errors::DomainError, value_objects::UserId};
|
||||
|
||||
use super::{commands::RejectFollowCommand, deps::SocialCommandDeps};
|
||||
|
||||
pub async fn execute(
|
||||
deps: &SocialCommandDeps,
|
||||
cmd: RejectFollowCommand,
|
||||
) -> Result<(), DomainError> {
|
||||
let owner = UserId::from_uuid(cmd.owner_id);
|
||||
deps.social_command
|
||||
.reject_follow(&owner, &cmd.requester)
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests/reject.rs"]
|
||||
mod tests;
|
||||
@@ -1,23 +0,0 @@
|
||||
use domain::{errors::DomainError, events::DomainEvent, value_objects::UserId};
|
||||
|
||||
use super::{commands::RemoveFollowerCommand, deps::SocialCommandDeps};
|
||||
|
||||
pub async fn execute(
|
||||
deps: &SocialCommandDeps,
|
||||
cmd: RemoveFollowerCommand,
|
||||
) -> Result<(), DomainError> {
|
||||
let owner = UserId::from_uuid(cmd.owner_id);
|
||||
deps.social_command
|
||||
.remove_follower(&owner, &cmd.follower)
|
||||
.await?;
|
||||
deps.event_publisher
|
||||
.publish(&DomainEvent::FollowerRemoved {
|
||||
owner,
|
||||
follower: cmd.follower,
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests/remove_follower.rs"]
|
||||
mod tests;
|
||||
@@ -1,65 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use domain::{
|
||||
events::DomainEvent,
|
||||
testing::{InMemorySocialRepository, NoopEventPublisher},
|
||||
value_objects::{FollowTarget, SocialIdentity, UserId},
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::social::{
|
||||
accept,
|
||||
commands::{AcceptFollowCommand, FollowCommand},
|
||||
deps::SocialCommandDeps,
|
||||
follow,
|
||||
};
|
||||
|
||||
fn make_deps() -> (
|
||||
Arc<InMemorySocialRepository>,
|
||||
Arc<NoopEventPublisher>,
|
||||
SocialCommandDeps,
|
||||
) {
|
||||
let social = InMemorySocialRepository::new();
|
||||
let events = NoopEventPublisher::new();
|
||||
let deps = SocialCommandDeps {
|
||||
social_command: Arc::clone(&social) as _,
|
||||
social_query: Arc::clone(&social) as _,
|
||||
event_publisher: Arc::clone(&events) as _,
|
||||
};
|
||||
(social, events, deps)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn accept_follow_emits_follow_accepted_event() {
|
||||
let (_social, events, deps) = make_deps();
|
||||
let follower_id = Uuid::new_v4();
|
||||
let owner_id = Uuid::new_v4();
|
||||
let requester = SocialIdentity::Local(UserId::from_uuid(follower_id));
|
||||
|
||||
follow::execute(
|
||||
&deps,
|
||||
FollowCommand {
|
||||
follower_id,
|
||||
target: FollowTarget::Identity(SocialIdentity::Local(UserId::from_uuid(owner_id))),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
accept::execute(
|
||||
&deps,
|
||||
AcceptFollowCommand {
|
||||
owner_id,
|
||||
requester,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let published = events.published();
|
||||
assert!(
|
||||
published
|
||||
.iter()
|
||||
.any(|e| matches!(e, DomainEvent::FollowAccepted { .. }))
|
||||
);
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use domain::{
|
||||
events::DomainEvent,
|
||||
testing::{InMemorySocialRepository, NoopEventPublisher},
|
||||
value_objects::{SocialIdentity, UserId},
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::social::{block, commands::BlockCommand, deps::SocialCommandDeps};
|
||||
|
||||
fn make_deps() -> (
|
||||
Arc<InMemorySocialRepository>,
|
||||
Arc<NoopEventPublisher>,
|
||||
SocialCommandDeps,
|
||||
) {
|
||||
let social = InMemorySocialRepository::new();
|
||||
let events = NoopEventPublisher::new();
|
||||
let deps = SocialCommandDeps {
|
||||
social_command: Arc::clone(&social) as _,
|
||||
social_query: Arc::clone(&social) as _,
|
||||
event_publisher: Arc::clone(&events) as _,
|
||||
};
|
||||
(social, events, deps)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn block_emits_actor_blocked_event() {
|
||||
let (_social, events, deps) = make_deps();
|
||||
|
||||
block::execute(
|
||||
&deps,
|
||||
BlockCommand {
|
||||
blocker_id: Uuid::new_v4(),
|
||||
target: SocialIdentity::Local(UserId::from_uuid(Uuid::new_v4())),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let published = events.published();
|
||||
assert!(
|
||||
published
|
||||
.iter()
|
||||
.any(|e| matches!(e, DomainEvent::ActorBlocked { .. }))
|
||||
);
|
||||
}
|
||||
449
crates/application/src/social/tests/execute.rs
Normal file
449
crates/application/src/social/tests/execute.rs
Normal file
@@ -0,0 +1,449 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use domain::{
|
||||
events::DomainEvent,
|
||||
testing::{InMemorySocialRepository, NoopEventPublisher},
|
||||
value_objects::{FollowTarget, SocialIdentity, UserId},
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::social::{
|
||||
commands::SocialCmd,
|
||||
deps::{SocialCommandDeps, SocialQueryDeps},
|
||||
execute::{execute_command, execute_query},
|
||||
queries::SocialQry,
|
||||
};
|
||||
|
||||
fn make_cmd_deps() -> (
|
||||
Arc<InMemorySocialRepository>,
|
||||
Arc<NoopEventPublisher>,
|
||||
SocialCommandDeps,
|
||||
) {
|
||||
let social = InMemorySocialRepository::new();
|
||||
let events = NoopEventPublisher::new();
|
||||
let deps = SocialCommandDeps {
|
||||
social_command: Arc::clone(&social) as _,
|
||||
social_query: Arc::clone(&social) as _,
|
||||
event_publisher: Arc::clone(&events) as _,
|
||||
};
|
||||
(social, events, deps)
|
||||
}
|
||||
|
||||
// ── Follow ──────────────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn follow_emits_follow_requested_event() {
|
||||
let (_social, events, deps) = make_cmd_deps();
|
||||
|
||||
execute_command(
|
||||
&deps,
|
||||
SocialCmd::Follow {
|
||||
follower_id: Uuid::new_v4(),
|
||||
target: FollowTarget::Identity(SocialIdentity::Local(UserId::from_uuid(
|
||||
Uuid::new_v4(),
|
||||
))),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let published = events.published();
|
||||
assert!(
|
||||
published
|
||||
.iter()
|
||||
.any(|e| matches!(e, DomainEvent::FollowRequested { .. }))
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn cannot_follow_yourself() {
|
||||
let (_social, _events, deps) = make_cmd_deps();
|
||||
let user_id = Uuid::new_v4();
|
||||
|
||||
let result = execute_command(
|
||||
&deps,
|
||||
SocialCmd::Follow {
|
||||
follower_id: user_id,
|
||||
target: FollowTarget::Identity(SocialIdentity::Local(UserId::from_uuid(user_id))),
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn cannot_follow_same_target_twice() {
|
||||
let (_social, _events, deps) = make_cmd_deps();
|
||||
let follower_id = Uuid::new_v4();
|
||||
let target = FollowTarget::Identity(SocialIdentity::Local(UserId::from_uuid(Uuid::new_v4())));
|
||||
|
||||
execute_command(
|
||||
&deps,
|
||||
SocialCmd::Follow {
|
||||
follower_id,
|
||||
target: target.clone(),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let result = execute_command(
|
||||
&deps,
|
||||
SocialCmd::Follow {
|
||||
follower_id,
|
||||
target,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
// ── Unfollow ────────────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn unfollow_emits_unfollowed_event() {
|
||||
let (_social, events, deps) = make_cmd_deps();
|
||||
let follower_id = Uuid::new_v4();
|
||||
let target = SocialIdentity::Local(UserId::from_uuid(Uuid::new_v4()));
|
||||
|
||||
execute_command(
|
||||
&deps,
|
||||
SocialCmd::Follow {
|
||||
follower_id,
|
||||
target: FollowTarget::Identity(target.clone()),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
execute_command(
|
||||
&deps,
|
||||
SocialCmd::Unfollow {
|
||||
follower_id,
|
||||
target,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let published = events.published();
|
||||
assert!(
|
||||
published
|
||||
.iter()
|
||||
.any(|e| matches!(e, DomainEvent::Unfollowed { .. }))
|
||||
);
|
||||
}
|
||||
|
||||
// ── Accept ──────────────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn accept_follow_emits_follow_accepted_event() {
|
||||
let (_social, events, deps) = make_cmd_deps();
|
||||
let follower_id = Uuid::new_v4();
|
||||
let owner_id = Uuid::new_v4();
|
||||
let requester = SocialIdentity::Local(UserId::from_uuid(follower_id));
|
||||
|
||||
execute_command(
|
||||
&deps,
|
||||
SocialCmd::Follow {
|
||||
follower_id,
|
||||
target: FollowTarget::Identity(SocialIdentity::Local(UserId::from_uuid(owner_id))),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
execute_command(
|
||||
&deps,
|
||||
SocialCmd::AcceptFollow {
|
||||
owner_id,
|
||||
requester,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let published = events.published();
|
||||
assert!(
|
||||
published
|
||||
.iter()
|
||||
.any(|e| matches!(e, DomainEvent::FollowAccepted { .. }))
|
||||
);
|
||||
}
|
||||
|
||||
// ── Reject ──────────────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn reject_follow_emits_follow_rejected_event() {
|
||||
let (_social, events, deps) = make_cmd_deps();
|
||||
let follower_id = Uuid::new_v4();
|
||||
let owner_id = Uuid::new_v4();
|
||||
|
||||
execute_command(
|
||||
&deps,
|
||||
SocialCmd::Follow {
|
||||
follower_id,
|
||||
target: FollowTarget::Identity(SocialIdentity::Local(UserId::from_uuid(owner_id))),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
execute_command(
|
||||
&deps,
|
||||
SocialCmd::RejectFollow {
|
||||
owner_id,
|
||||
requester: SocialIdentity::Local(UserId::from_uuid(follower_id)),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let published = events.published();
|
||||
assert!(
|
||||
published
|
||||
.iter()
|
||||
.any(|e| matches!(e, DomainEvent::FollowRejected { .. }))
|
||||
);
|
||||
}
|
||||
|
||||
// ── Remove follower ─────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn remove_follower_emits_follower_removed_event() {
|
||||
let (_social, events, deps) = make_cmd_deps();
|
||||
let follower_id = Uuid::new_v4();
|
||||
let owner_id = Uuid::new_v4();
|
||||
|
||||
execute_command(
|
||||
&deps,
|
||||
SocialCmd::Follow {
|
||||
follower_id,
|
||||
target: FollowTarget::Identity(SocialIdentity::Local(UserId::from_uuid(owner_id))),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
execute_command(
|
||||
&deps,
|
||||
SocialCmd::AcceptFollow {
|
||||
owner_id,
|
||||
requester: SocialIdentity::Local(UserId::from_uuid(follower_id)),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
execute_command(
|
||||
&deps,
|
||||
SocialCmd::RemoveFollower {
|
||||
owner_id,
|
||||
follower: SocialIdentity::Local(UserId::from_uuid(follower_id)),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let published = events.published();
|
||||
assert!(
|
||||
published
|
||||
.iter()
|
||||
.any(|e| matches!(e, DomainEvent::FollowerRemoved { .. }))
|
||||
);
|
||||
}
|
||||
|
||||
// ── Block ───────────────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn block_emits_actor_blocked_event() {
|
||||
let (_social, events, deps) = make_cmd_deps();
|
||||
|
||||
execute_command(
|
||||
&deps,
|
||||
SocialCmd::Block {
|
||||
blocker_id: Uuid::new_v4(),
|
||||
target: SocialIdentity::Local(UserId::from_uuid(Uuid::new_v4())),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let published = events.published();
|
||||
assert!(
|
||||
published
|
||||
.iter()
|
||||
.any(|e| matches!(e, DomainEvent::ActorBlocked { .. }))
|
||||
);
|
||||
}
|
||||
|
||||
// ── Unblock ─────────────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn unblock_emits_actor_unblocked_event() {
|
||||
let (_social, events, deps) = make_cmd_deps();
|
||||
let target = SocialIdentity::Local(UserId::from_uuid(Uuid::new_v4()));
|
||||
let blocker_id = Uuid::new_v4();
|
||||
|
||||
execute_command(
|
||||
&deps,
|
||||
SocialCmd::Block {
|
||||
blocker_id,
|
||||
target: target.clone(),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
execute_command(&deps, SocialCmd::Unblock { blocker_id, target })
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let published = events.published();
|
||||
assert!(
|
||||
published
|
||||
.iter()
|
||||
.any(|e| matches!(e, DomainEvent::ActorUnblocked { .. }))
|
||||
);
|
||||
}
|
||||
|
||||
// ── Get following ───────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn returns_accepted_follows() {
|
||||
let social = InMemorySocialRepository::new();
|
||||
let events = NoopEventPublisher::new();
|
||||
let cmd_deps = SocialCommandDeps {
|
||||
social_command: Arc::clone(&social) as _,
|
||||
social_query: Arc::clone(&social) as _,
|
||||
event_publisher: Arc::clone(&events) as _,
|
||||
};
|
||||
let query_deps = SocialQueryDeps {
|
||||
social_query: Arc::clone(&social) as _,
|
||||
};
|
||||
|
||||
let follower_id = Uuid::new_v4();
|
||||
let target_id = Uuid::new_v4();
|
||||
|
||||
execute_command(
|
||||
&cmd_deps,
|
||||
SocialCmd::Follow {
|
||||
follower_id,
|
||||
target: FollowTarget::Identity(SocialIdentity::Local(UserId::from_uuid(target_id))),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Pending follow should not appear
|
||||
let following = execute_query(
|
||||
&query_deps,
|
||||
SocialQry::GetFollowing {
|
||||
user_id: follower_id,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(following.is_empty());
|
||||
|
||||
// Accept, then it should appear
|
||||
execute_command(
|
||||
&cmd_deps,
|
||||
SocialCmd::AcceptFollow {
|
||||
owner_id: target_id,
|
||||
requester: SocialIdentity::Local(UserId::from_uuid(follower_id)),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let following = execute_query(
|
||||
&query_deps,
|
||||
SocialQry::GetFollowing {
|
||||
user_id: follower_id,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(following.len(), 1);
|
||||
}
|
||||
|
||||
// ── Get followers ───────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn returns_accepted_followers() {
|
||||
let social = InMemorySocialRepository::new();
|
||||
let events = NoopEventPublisher::new();
|
||||
let cmd_deps = SocialCommandDeps {
|
||||
social_command: Arc::clone(&social) as _,
|
||||
social_query: Arc::clone(&social) as _,
|
||||
event_publisher: Arc::clone(&events) as _,
|
||||
};
|
||||
let query_deps = SocialQueryDeps {
|
||||
social_query: Arc::clone(&social) as _,
|
||||
};
|
||||
|
||||
let follower_id = Uuid::new_v4();
|
||||
let owner_id = Uuid::new_v4();
|
||||
|
||||
execute_command(
|
||||
&cmd_deps,
|
||||
SocialCmd::Follow {
|
||||
follower_id,
|
||||
target: FollowTarget::Identity(SocialIdentity::Local(UserId::from_uuid(owner_id))),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
execute_command(
|
||||
&cmd_deps,
|
||||
SocialCmd::AcceptFollow {
|
||||
owner_id,
|
||||
requester: SocialIdentity::Local(UserId::from_uuid(follower_id)),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let followers = execute_query(&query_deps, SocialQry::GetFollowers { user_id: owner_id })
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(followers.len(), 1);
|
||||
}
|
||||
|
||||
// ── Get pending ─────────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn returns_only_pending_followers() {
|
||||
let social = InMemorySocialRepository::new();
|
||||
let events = NoopEventPublisher::new();
|
||||
let cmd_deps = SocialCommandDeps {
|
||||
social_command: Arc::clone(&social) as _,
|
||||
social_query: Arc::clone(&social) as _,
|
||||
event_publisher: Arc::clone(&events) as _,
|
||||
};
|
||||
let query_deps = SocialQueryDeps {
|
||||
social_query: Arc::clone(&social) as _,
|
||||
};
|
||||
|
||||
let follower_id = Uuid::new_v4();
|
||||
let owner_id = Uuid::new_v4();
|
||||
|
||||
execute_command(
|
||||
&cmd_deps,
|
||||
SocialCmd::Follow {
|
||||
follower_id,
|
||||
target: FollowTarget::Identity(SocialIdentity::Local(UserId::from_uuid(owner_id))),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let pending = execute_query(&query_deps, SocialQry::GetPending { user_id: owner_id })
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(pending.len(), 1);
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use domain::{
|
||||
events::DomainEvent,
|
||||
testing::{InMemorySocialRepository, NoopEventPublisher},
|
||||
value_objects::{FollowTarget, SocialIdentity, UserId},
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::social::{commands::FollowCommand, deps::SocialCommandDeps, follow};
|
||||
|
||||
fn make_deps() -> (
|
||||
Arc<InMemorySocialRepository>,
|
||||
Arc<NoopEventPublisher>,
|
||||
SocialCommandDeps,
|
||||
) {
|
||||
let social = InMemorySocialRepository::new();
|
||||
let events = NoopEventPublisher::new();
|
||||
let deps = SocialCommandDeps {
|
||||
social_command: Arc::clone(&social) as _,
|
||||
social_query: Arc::clone(&social) as _,
|
||||
event_publisher: Arc::clone(&events) as _,
|
||||
};
|
||||
(social, events, deps)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn follow_emits_follow_requested_event() {
|
||||
let (_social, events, deps) = make_deps();
|
||||
|
||||
follow::execute(
|
||||
&deps,
|
||||
FollowCommand {
|
||||
follower_id: Uuid::new_v4(),
|
||||
target: FollowTarget::Identity(SocialIdentity::Local(UserId::from_uuid(Uuid::new_v4()))),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let published = events.published();
|
||||
assert!(
|
||||
published
|
||||
.iter()
|
||||
.any(|e| matches!(e, DomainEvent::FollowRequested { .. }))
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn cannot_follow_yourself() {
|
||||
let (_social, _events, deps) = make_deps();
|
||||
let user_id = Uuid::new_v4();
|
||||
|
||||
let result = follow::execute(
|
||||
&deps,
|
||||
FollowCommand {
|
||||
follower_id: user_id,
|
||||
target: FollowTarget::Identity(SocialIdentity::Local(UserId::from_uuid(user_id))),
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn cannot_follow_same_target_twice() {
|
||||
let (_social, _events, deps) = make_deps();
|
||||
let follower_id = Uuid::new_v4();
|
||||
let target = FollowTarget::Identity(SocialIdentity::Local(UserId::from_uuid(Uuid::new_v4())));
|
||||
|
||||
follow::execute(
|
||||
&deps,
|
||||
FollowCommand {
|
||||
follower_id,
|
||||
target: target.clone(),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let result = follow::execute(
|
||||
&deps,
|
||||
FollowCommand {
|
||||
follower_id,
|
||||
target,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use domain::{
|
||||
testing::{InMemorySocialRepository, NoopEventPublisher},
|
||||
value_objects::{FollowTarget, SocialIdentity, UserId},
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::social::{
|
||||
accept,
|
||||
commands::{AcceptFollowCommand, FollowCommand},
|
||||
deps::{SocialCommandDeps, SocialQueryDeps},
|
||||
follow, get_followers,
|
||||
queries::GetFollowersQuery,
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
async fn returns_accepted_followers() {
|
||||
let social = InMemorySocialRepository::new();
|
||||
let events = NoopEventPublisher::new();
|
||||
let cmd_deps = SocialCommandDeps {
|
||||
social_command: Arc::clone(&social) as _,
|
||||
social_query: Arc::clone(&social) as _,
|
||||
event_publisher: Arc::clone(&events) as _,
|
||||
};
|
||||
let query_deps = SocialQueryDeps {
|
||||
social_query: Arc::clone(&social) as _,
|
||||
};
|
||||
|
||||
let follower_id = Uuid::new_v4();
|
||||
let owner_id = Uuid::new_v4();
|
||||
|
||||
follow::execute(
|
||||
&cmd_deps,
|
||||
FollowCommand {
|
||||
follower_id,
|
||||
target: FollowTarget::Identity(SocialIdentity::Local(UserId::from_uuid(owner_id))),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
accept::execute(
|
||||
&cmd_deps,
|
||||
AcceptFollowCommand {
|
||||
owner_id,
|
||||
requester: SocialIdentity::Local(UserId::from_uuid(follower_id)),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let followers = get_followers::execute(&query_deps, GetFollowersQuery { user_id: owner_id })
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(followers.len(), 1);
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use domain::{
|
||||
testing::{InMemorySocialRepository, NoopEventPublisher},
|
||||
value_objects::{FollowTarget, SocialIdentity, UserId},
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::social::{
|
||||
accept,
|
||||
commands::{AcceptFollowCommand, FollowCommand},
|
||||
deps::{SocialCommandDeps, SocialQueryDeps},
|
||||
follow, get_following,
|
||||
queries::GetFollowingQuery,
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
async fn returns_accepted_follows() {
|
||||
let social = InMemorySocialRepository::new();
|
||||
let events = NoopEventPublisher::new();
|
||||
let cmd_deps = SocialCommandDeps {
|
||||
social_command: Arc::clone(&social) as _,
|
||||
social_query: Arc::clone(&social) as _,
|
||||
event_publisher: Arc::clone(&events) as _,
|
||||
};
|
||||
let query_deps = SocialQueryDeps {
|
||||
social_query: Arc::clone(&social) as _,
|
||||
};
|
||||
|
||||
let follower_id = Uuid::new_v4();
|
||||
let target_id = Uuid::new_v4();
|
||||
|
||||
follow::execute(
|
||||
&cmd_deps,
|
||||
FollowCommand {
|
||||
follower_id,
|
||||
target: FollowTarget::Identity(SocialIdentity::Local(UserId::from_uuid(target_id))),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Pending follow should not appear
|
||||
let following = get_following::execute(
|
||||
&query_deps,
|
||||
GetFollowingQuery {
|
||||
user_id: follower_id,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(following.is_empty());
|
||||
|
||||
// Accept, then it should appear
|
||||
accept::execute(
|
||||
&cmd_deps,
|
||||
AcceptFollowCommand {
|
||||
owner_id: target_id,
|
||||
requester: SocialIdentity::Local(UserId::from_uuid(follower_id)),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let following = get_following::execute(
|
||||
&query_deps,
|
||||
GetFollowingQuery {
|
||||
user_id: follower_id,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(following.len(), 1);
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use domain::{
|
||||
testing::{InMemorySocialRepository, NoopEventPublisher},
|
||||
value_objects::{FollowTarget, SocialIdentity, UserId},
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::social::{
|
||||
commands::FollowCommand,
|
||||
deps::{SocialCommandDeps, SocialQueryDeps},
|
||||
follow, get_pending,
|
||||
queries::GetPendingFollowersQuery,
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
async fn returns_only_pending_followers() {
|
||||
let social = InMemorySocialRepository::new();
|
||||
let events = NoopEventPublisher::new();
|
||||
let cmd_deps = SocialCommandDeps {
|
||||
social_command: Arc::clone(&social) as _,
|
||||
social_query: Arc::clone(&social) as _,
|
||||
event_publisher: Arc::clone(&events) as _,
|
||||
};
|
||||
let query_deps = SocialQueryDeps {
|
||||
social_query: Arc::clone(&social) as _,
|
||||
};
|
||||
|
||||
let follower_id = Uuid::new_v4();
|
||||
let owner_id = Uuid::new_v4();
|
||||
|
||||
follow::execute(
|
||||
&cmd_deps,
|
||||
FollowCommand {
|
||||
follower_id,
|
||||
target: FollowTarget::Identity(SocialIdentity::Local(UserId::from_uuid(owner_id))),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let pending = get_pending::execute(&query_deps, GetPendingFollowersQuery { user_id: owner_id })
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(pending.len(), 1);
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use domain::{
|
||||
testing::{InMemorySocialRepository, NoopEventPublisher},
|
||||
value_objects::{FollowTarget, SocialIdentity, UserId},
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::social::{
|
||||
commands::{FollowCommand, RejectFollowCommand},
|
||||
deps::SocialCommandDeps,
|
||||
follow, reject,
|
||||
};
|
||||
|
||||
fn make_deps() -> (
|
||||
Arc<InMemorySocialRepository>,
|
||||
Arc<NoopEventPublisher>,
|
||||
SocialCommandDeps,
|
||||
) {
|
||||
let social = InMemorySocialRepository::new();
|
||||
let events = NoopEventPublisher::new();
|
||||
let deps = SocialCommandDeps {
|
||||
social_command: Arc::clone(&social) as _,
|
||||
social_query: Arc::clone(&social) as _,
|
||||
event_publisher: Arc::clone(&events) as _,
|
||||
};
|
||||
(social, events, deps)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn reject_follow_completes_without_error() {
|
||||
let (_social, _events, deps) = make_deps();
|
||||
let follower_id = Uuid::new_v4();
|
||||
let owner_id = Uuid::new_v4();
|
||||
|
||||
follow::execute(
|
||||
&deps,
|
||||
FollowCommand {
|
||||
follower_id,
|
||||
target: FollowTarget::Identity(SocialIdentity::Local(UserId::from_uuid(owner_id))),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
reject::execute(
|
||||
&deps,
|
||||
RejectFollowCommand {
|
||||
owner_id,
|
||||
requester: SocialIdentity::Local(UserId::from_uuid(follower_id)),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use domain::{
|
||||
events::DomainEvent,
|
||||
testing::{InMemorySocialRepository, NoopEventPublisher},
|
||||
value_objects::{FollowTarget, SocialIdentity, UserId},
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::social::{
|
||||
accept,
|
||||
commands::{AcceptFollowCommand, FollowCommand, RemoveFollowerCommand},
|
||||
deps::SocialCommandDeps,
|
||||
follow, remove_follower,
|
||||
};
|
||||
|
||||
fn make_deps() -> (
|
||||
Arc<InMemorySocialRepository>,
|
||||
Arc<NoopEventPublisher>,
|
||||
SocialCommandDeps,
|
||||
) {
|
||||
let social = InMemorySocialRepository::new();
|
||||
let events = NoopEventPublisher::new();
|
||||
let deps = SocialCommandDeps {
|
||||
social_command: Arc::clone(&social) as _,
|
||||
social_query: Arc::clone(&social) as _,
|
||||
event_publisher: Arc::clone(&events) as _,
|
||||
};
|
||||
(social, events, deps)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn remove_follower_emits_follower_removed_event() {
|
||||
let (_social, events, deps) = make_deps();
|
||||
let follower_id = Uuid::new_v4();
|
||||
let owner_id = Uuid::new_v4();
|
||||
|
||||
follow::execute(
|
||||
&deps,
|
||||
FollowCommand {
|
||||
follower_id,
|
||||
target: FollowTarget::Identity(SocialIdentity::Local(UserId::from_uuid(owner_id))),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
accept::execute(
|
||||
&deps,
|
||||
AcceptFollowCommand {
|
||||
owner_id,
|
||||
requester: SocialIdentity::Local(UserId::from_uuid(follower_id)),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
remove_follower::execute(
|
||||
&deps,
|
||||
RemoveFollowerCommand {
|
||||
owner_id,
|
||||
follower: SocialIdentity::Local(UserId::from_uuid(follower_id)),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let published = events.published();
|
||||
assert!(
|
||||
published
|
||||
.iter()
|
||||
.any(|e| matches!(e, DomainEvent::FollowerRemoved { .. }))
|
||||
);
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use domain::{
|
||||
events::DomainEvent,
|
||||
testing::{InMemorySocialRepository, NoopEventPublisher},
|
||||
value_objects::{SocialIdentity, UserId},
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::social::{
|
||||
block,
|
||||
commands::{BlockCommand, UnblockCommand},
|
||||
deps::SocialCommandDeps,
|
||||
unblock,
|
||||
};
|
||||
|
||||
fn make_deps() -> (
|
||||
Arc<InMemorySocialRepository>,
|
||||
Arc<NoopEventPublisher>,
|
||||
SocialCommandDeps,
|
||||
) {
|
||||
let social = InMemorySocialRepository::new();
|
||||
let events = NoopEventPublisher::new();
|
||||
let deps = SocialCommandDeps {
|
||||
social_command: Arc::clone(&social) as _,
|
||||
social_query: Arc::clone(&social) as _,
|
||||
event_publisher: Arc::clone(&events) as _,
|
||||
};
|
||||
(social, events, deps)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn unblock_emits_actor_unblocked_event() {
|
||||
let (_social, events, deps) = make_deps();
|
||||
let target = SocialIdentity::Local(UserId::from_uuid(Uuid::new_v4()));
|
||||
let blocker_id = Uuid::new_v4();
|
||||
|
||||
block::execute(
|
||||
&deps,
|
||||
BlockCommand {
|
||||
blocker_id,
|
||||
target: target.clone(),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
unblock::execute(&deps, UnblockCommand { blocker_id, target })
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let published = events.published();
|
||||
assert!(
|
||||
published
|
||||
.iter()
|
||||
.any(|e| matches!(e, DomainEvent::ActorUnblocked { .. }))
|
||||
);
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use domain::{
|
||||
events::DomainEvent,
|
||||
testing::{InMemorySocialRepository, NoopEventPublisher},
|
||||
value_objects::{FollowTarget, SocialIdentity, UserId},
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::social::{
|
||||
commands::{FollowCommand, UnfollowCommand},
|
||||
deps::SocialCommandDeps,
|
||||
follow, unfollow,
|
||||
};
|
||||
|
||||
fn make_deps() -> (
|
||||
Arc<InMemorySocialRepository>,
|
||||
Arc<NoopEventPublisher>,
|
||||
SocialCommandDeps,
|
||||
) {
|
||||
let social = InMemorySocialRepository::new();
|
||||
let events = NoopEventPublisher::new();
|
||||
let deps = SocialCommandDeps {
|
||||
social_command: Arc::clone(&social) as _,
|
||||
social_query: Arc::clone(&social) as _,
|
||||
event_publisher: Arc::clone(&events) as _,
|
||||
};
|
||||
(social, events, deps)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn unfollow_emits_unfollowed_event() {
|
||||
let (_social, events, deps) = make_deps();
|
||||
let follower_id = Uuid::new_v4();
|
||||
let target = SocialIdentity::Local(UserId::from_uuid(Uuid::new_v4()));
|
||||
|
||||
follow::execute(
|
||||
&deps,
|
||||
FollowCommand {
|
||||
follower_id,
|
||||
target: FollowTarget::Identity(target.clone()),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
unfollow::execute(
|
||||
&deps,
|
||||
UnfollowCommand {
|
||||
follower_id,
|
||||
target,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let published = events.published();
|
||||
assert!(
|
||||
published
|
||||
.iter()
|
||||
.any(|e| matches!(e, DomainEvent::Unfollowed { .. }))
|
||||
);
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
use domain::{errors::DomainError, events::DomainEvent, value_objects::UserId};
|
||||
|
||||
use super::{commands::UnblockCommand, deps::SocialCommandDeps};
|
||||
|
||||
pub async fn execute(deps: &SocialCommandDeps, cmd: UnblockCommand) -> Result<(), DomainError> {
|
||||
let blocker = UserId::from_uuid(cmd.blocker_id);
|
||||
deps.social_command.unblock(&blocker, &cmd.target).await?;
|
||||
deps.event_publisher
|
||||
.publish(&DomainEvent::ActorUnblocked {
|
||||
blocker,
|
||||
target: cmd.target,
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests/unblock.rs"]
|
||||
mod tests;
|
||||
@@ -1,18 +0,0 @@
|
||||
use domain::{errors::DomainError, events::DomainEvent, value_objects::UserId};
|
||||
|
||||
use super::{commands::UnfollowCommand, deps::SocialCommandDeps};
|
||||
|
||||
pub async fn execute(deps: &SocialCommandDeps, cmd: UnfollowCommand) -> Result<(), DomainError> {
|
||||
let follower = UserId::from_uuid(cmd.follower_id);
|
||||
deps.social_command.unfollow(&follower, &cmd.target).await?;
|
||||
deps.event_publisher
|
||||
.publish(&DomainEvent::Unfollowed {
|
||||
follower,
|
||||
target: cmd.target,
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests/unfollow.rs"]
|
||||
mod tests;
|
||||
@@ -74,6 +74,7 @@ impl EventHandler for RecordingHandler {
|
||||
}
|
||||
DomainEvent::FollowRequested { .. } => "follow_requested",
|
||||
DomainEvent::FollowAccepted { .. } => "follow_accepted",
|
||||
DomainEvent::FollowRejected { .. } => "follow_rejected",
|
||||
DomainEvent::Unfollowed { .. } => "unfollowed",
|
||||
DomainEvent::FollowerRemoved { .. } => "follower_removed",
|
||||
DomainEvent::ActorBlocked { .. } => "actor_blocked",
|
||||
|
||||
Reference in New Issue
Block a user