- Thought::new_local → NewThought struct (7 args → 1) - UserWriter::update_profile → UpdateProfileInput struct (6 args → 2) - update_profile use case → UpdateProfileInput (8 args → 3) - ActivityPubService::new → builder pattern (9 args → 5 required + 4 optional setters) - accept_note → AcceptNoteInput struct (8 args → 1) - ThoughtNote::new_public → ThoughtNoteInput struct (8 args → 1) Remove all #[allow(clippy::too_many_arguments)] annotations.
207 lines
5.7 KiB
Rust
207 lines
5.7 KiB
Rust
use super::*;
|
|
use domain::{
|
|
models::{
|
|
thought::{NewThought, Thought, Visibility},
|
|
user::User,
|
|
},
|
|
testing::TestStore,
|
|
value_objects::*,
|
|
};
|
|
|
|
fn user(name: &str) -> User {
|
|
User::new_local(
|
|
UserId::new(),
|
|
Username::new(name).unwrap(),
|
|
Email::new(format!("{name}@ex.com")).unwrap(),
|
|
PasswordHash("h".into()),
|
|
)
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn like_and_unlike() {
|
|
let store = TestStore::default();
|
|
let alice = user("alice");
|
|
let tid = ThoughtId::new();
|
|
store
|
|
.thoughts
|
|
.lock()
|
|
.unwrap()
|
|
.push(Thought::new_local(NewThought {
|
|
id: tid.clone(),
|
|
user_id: alice.id.clone(),
|
|
content: Content::new_local("hi").unwrap(),
|
|
in_reply_to_id: None,
|
|
visibility: Visibility::Public,
|
|
content_warning: None,
|
|
sensitive: false,
|
|
}));
|
|
like_thought(&store, &store, &alice.id, &tid).await.unwrap();
|
|
assert_eq!(store.likes.lock().unwrap().len(), 1);
|
|
unlike_thought(&store, &store, &alice.id, &tid)
|
|
.await
|
|
.unwrap();
|
|
assert!(store.likes.lock().unwrap().is_empty());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn follow_and_unfollow() {
|
|
let store = TestStore::default();
|
|
let alice = user("alice");
|
|
let bob = user("bob");
|
|
follow_user(&store, &store, &alice.id, &bob.id)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(store.follows.lock().unwrap().len(), 1);
|
|
unfollow_user(&store, &store, &alice.id, &bob.id)
|
|
.await
|
|
.unwrap();
|
|
assert!(store.follows.lock().unwrap().is_empty());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn cannot_follow_self() {
|
|
let store = TestStore::default();
|
|
let alice = user("alice");
|
|
let err = follow_user(&store, &store, &alice.id, &alice.id)
|
|
.await
|
|
.unwrap_err();
|
|
assert!(matches!(err, DomainError::InvalidInput(_)));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn unblock_user_publishes_event() {
|
|
let store = TestStore::default();
|
|
let alice = user("alice");
|
|
let bob = user("bob");
|
|
block_user(&store, &store, &alice.id, &bob.id)
|
|
.await
|
|
.unwrap();
|
|
store.events.lock().unwrap().clear();
|
|
unblock_user(&store, &store, &alice.id, &bob.id)
|
|
.await
|
|
.unwrap();
|
|
let events = store.events.lock().unwrap();
|
|
assert_eq!(events.len(), 1);
|
|
assert!(matches!(events[0], DomainEvent::UserUnblocked { .. }));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn block_user_saves_block_and_publishes_event() {
|
|
let store = TestStore::default();
|
|
let alice = user("alice");
|
|
let bob = user("bob");
|
|
block_user(&store, &store, &alice.id, &bob.id)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(store.blocks.lock().unwrap().len(), 1);
|
|
let events = store.events.lock().unwrap();
|
|
assert!(events.iter().any(
|
|
|e| matches!(e, DomainEvent::UserBlocked { blocker_id, .. } if blocker_id == &alice.id)
|
|
));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn cannot_block_self() {
|
|
let store = TestStore::default();
|
|
let alice = user("alice");
|
|
let err = block_user(&store, &store, &alice.id, &alice.id)
|
|
.await
|
|
.unwrap_err();
|
|
assert!(matches!(err, DomainError::InvalidInput(_)));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn follow_actor_local_routes_to_follow_user() {
|
|
let store = TestStore::default();
|
|
let alice = user("alice");
|
|
let bob = user("bob");
|
|
store.users.lock().unwrap().push(bob.clone());
|
|
follow_actor(&store, &store, &store, &store, &alice.id, "bob")
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(store.follows.lock().unwrap().len(), 1);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn follow_actor_remote_routes_to_federation() {
|
|
let store = TestStore::default();
|
|
let alice = user("alice");
|
|
follow_actor(
|
|
&store,
|
|
&store,
|
|
&store,
|
|
&store,
|
|
&alice.id,
|
|
"@bob@example.com",
|
|
)
|
|
.await
|
|
.unwrap();
|
|
// TestStore.follow_remote is a no-op that returns Ok(())
|
|
// no local follow should be recorded
|
|
assert!(store.follows.lock().unwrap().is_empty());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn unfollow_actor_local_routes_to_unfollow_user() {
|
|
let store = TestStore::default();
|
|
let alice = user("alice");
|
|
let bob = user("bob");
|
|
store.users.lock().unwrap().push(bob.clone());
|
|
// Create an existing follow first
|
|
store
|
|
.follows
|
|
.lock()
|
|
.unwrap()
|
|
.push(domain::models::social::Follow {
|
|
follower_id: alice.id.clone(),
|
|
following_id: bob.id.clone(),
|
|
state: domain::models::social::FollowState::Accepted,
|
|
ap_id: None,
|
|
created_at: chrono::Utc::now(),
|
|
});
|
|
unfollow_actor(&store, &store, &store, &store, &alice.id, "bob")
|
|
.await
|
|
.unwrap();
|
|
assert!(store.follows.lock().unwrap().is_empty());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn unfollow_actor_remote_routes_to_federation() {
|
|
let store = TestStore::default();
|
|
let alice = user("alice");
|
|
unfollow_actor(
|
|
&store,
|
|
&store,
|
|
&store,
|
|
&store,
|
|
&alice.id,
|
|
"@bob@example.com",
|
|
)
|
|
.await
|
|
.unwrap();
|
|
// TestStore.unfollow_remote is a no-op — just verify it doesn't error
|
|
assert!(store.follows.lock().unwrap().is_empty());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn boost_and_unboost() {
|
|
let store = TestStore::default();
|
|
let alice = user("alice");
|
|
let tid = ThoughtId::new();
|
|
boost_thought(&store, &store, &alice.id, &tid)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(store.boosts.lock().unwrap().len(), 1);
|
|
unboost_thought(&store, &store, &alice.id, &tid)
|
|
.await
|
|
.unwrap();
|
|
assert!(store.boosts.lock().unwrap().is_empty());
|
|
let events = store.events.lock().unwrap();
|
|
assert!(events
|
|
.iter()
|
|
.any(|e| matches!(e, DomainEvent::BoostAdded { .. })));
|
|
assert!(events
|
|
.iter()
|
|
.any(|e| matches!(e, DomainEvent::BoostRemoved { .. })));
|
|
}
|