Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Federation tests replication round1 - demonstrate absent replication of comment deletes #3657

Merged
merged 18 commits into from
Jul 27, 2023
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
fb7df85
more robust test of unlike a comment, confirm replication to instance…
RocketDerp Jul 18, 2023
83cdac2
more robust 'delete a comment' test, confirm replication
RocketDerp Jul 18, 2023
7bd3b20
Far more robust "Report a comment" test. Many comments about situatio…
RocketDerp Jul 18, 2023
7dfb6ee
typo and actually have Gamma comment check use gamma, not alpha
RocketDerp Jul 18, 2023
0384675
prepare-drone-federation-test.sh has some more echo output and note a…
RocketDerp Jul 19, 2023
9f742e2
Add http cache for webfingers (#3317)
cetra3 Jul 19, 2023
9769721
Merge branch 'main' into RocketDerp-federation_tests_replication_round1
Nutomic Jul 20, 2023
71d5ae7
Rewrite activity lists to fix delete federation (fixes #3625)
Nutomic Jul 20, 2023
5cd2852
Revert "typo and actually have Gamma comment check use gamma, not alpha"
RocketDerp Jul 20, 2023
a72590b
Revert "Far more robust "Report a comment" test. Many comments about …
RocketDerp Jul 20, 2023
6c74f9f
Merge branch 'LemmyNet:main' into federation_tests_replication_round1
RocketDerp Jul 20, 2023
fb32d19
Merge branch 'LemmyNet:main' into federation_tests_replication_round1
RocketDerp Jul 20, 2023
24f3d02
prettier TypeScript
RocketDerp Jul 20, 2023
bdb7381
revised comments, as ResolveObject isn't using routine replication
RocketDerp Jul 20, 2023
46b7e6a
fmt
Nutomic Jul 21, 2023
a3658a9
Merge branch 'main' into federation_tests_replication_round1
dessalines Jul 26, 2023
9cb9ca3
fix api tests
Nutomic Jul 27, 2023
4e741fb
remove comment
Nutomic Jul 27, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 15 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

49 changes: 48 additions & 1 deletion api_tests/src/comment.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,27 @@ test("Update a comment", async () => {
});

test("Delete a comment", async () => {
// creating a comment on alpha (remote from home of community)
let commentRes = await createComment(alpha, postRes.post_view.post.id);

// Find the comment on beta (home of community)
let betaComment = (
await resolveComment(beta, commentRes.comment_view.comment)
).comment;

if (!betaComment) {
throw "Missing beta comment before delete";
}

// Find the comment on remote instance gamma
let gammaComment = (
await resolveComment(gamma, commentRes.comment_view.comment)
).comment;
Copy link
Member

@Nutomic Nutomic Jul 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isnt really testing replication. If the comment failed to be federated, resolve will fetch it instead. You could instead use resolvePost and then getComments for that post to check if its included there. Or add a new getComments variant which retrieves all newest comments for the whole instance.

This comment was marked as abuse.


if (!gammaComment) {
throw "Missing gamma comment (remote-home-remote replication) before delete";
}

let deleteCommentRes = await deleteComment(
alpha,
true,
Expand All @@ -126,6 +145,12 @@ test("Delete a comment", async () => {
resolveComment(beta, commentRes.comment_view.comment),
).rejects.toBe("couldnt_find_object");

// Make sure that comment is undefined on gamma after delete
await expect(
resolveComment(gamma, commentRes.comment_view.comment),
).rejects.toBe("couldnt_find_object");

// Test undeleting the comment
let undeleteCommentRes = await deleteComment(
alpha,
false,
Expand Down Expand Up @@ -225,17 +250,39 @@ test("Remove a comment from admin and community on different instance", async ()

test("Unlike a comment", async () => {
let commentRes = await createComment(alpha, postRes.post_view.post.id);

// Lemmy automatically creates 1 like (vote) by author of comment.
// Make sure that comment is liked (voted up) on gamma, downstream peer
// This is testing replication from remote-home-remote (alpha-beta-gamma)
let gammaComment1 = (
await resolveComment(gamma, commentRes.comment_view.comment)
).comment;
expect(gammaComment1).toBeDefined();
expect(gammaComment1?.community.local).toBe(false);
expect(gammaComment1?.creator.local).toBe(false);
expect(gammaComment1?.counts.score).toBe(1);

let unlike = await likeComment(alpha, 0, commentRes.comment_view.comment);
expect(unlike.comment_view.counts.score).toBe(0);

// Make sure that post is unliked on beta
// Make sure that comment is unliked on beta
let betaComment = (
await resolveComment(beta, commentRes.comment_view.comment)
).comment;
expect(betaComment).toBeDefined();
expect(betaComment?.community.local).toBe(true);
expect(betaComment?.creator.local).toBe(false);
expect(betaComment?.counts.score).toBe(0);

// Make sure that comment is unliked on gamma, downstream peer
// This is testing replication from remote-home-remote (alpha-beta-gamma)
let gammaComment = (
await resolveComment(gamma, commentRes.comment_view.comment)
).comment;
expect(gammaComment).toBeDefined();
expect(gammaComment?.community.local).toBe(false);
expect(gammaComment?.creator.local).toBe(false);
expect(gammaComment?.counts.score).toBe(0);
});

test("Federated comment like", async () => {
Expand Down
18 changes: 10 additions & 8 deletions crates/apub/src/activities/community/announce.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,17 +50,19 @@ impl ActivityHandler for RawAnnouncableActivities {
if let AnnouncableActivities::Page(_) = activity {
return Err(LemmyErrorType::CannotReceivePage)?;
}
let community = activity.community(data).await?;
let actor_id = activity.actor().clone().into();

// verify and receive activity
activity.verify(data).await?;
activity.receive(data).await?;

// send to community followers
if community.local {
verify_person_in_community(&actor_id, &community, data).await?;
AnnounceActivity::send(self, &community, data).await?;
activity.clone().receive(data).await?;

// if activity is in a community, send to followers
let community = activity.community(data).await;
if let Ok(community) = community {
if community.local {
let actor_id = activity.actor().clone().into();
verify_person_in_community(&actor_id, &community, data).await?;
AnnounceActivity::send(self, &community, data).await?;
}
}
Ok(())
}
Expand Down
53 changes: 24 additions & 29 deletions crates/apub/src/activity_lists.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,35 +24,45 @@ use crate::{
InCommunity,
},
};
use activitypub_federation::{
config::Data,
protocol::context::WithContext,
traits::ActivityHandler,
};
use activitypub_federation::{config::Data, traits::ActivityHandler};
use lemmy_api_common::context::LemmyContext;
use lemmy_utils::error::LemmyError;
use serde::{Deserialize, Serialize};
use url::Url;

/// List of activities which the shared inbox can handle.
///
/// This could theoretically be defined as an enum with variants `GroupInboxActivities` and
/// `PersonInboxActivities`. In practice we need to write it out manually so that priorities
/// are handled correctly.
#[derive(Debug, Deserialize, Serialize)]
#[serde(untagged)]
#[enum_delegate::implement(ActivityHandler)]
pub enum SharedInboxActivities {
PersonInboxActivities(Box<WithContext<PersonInboxActivities>>),
GroupInboxActivities(Box<WithContext<GroupInboxActivities>>),
Follow(Follow),
AcceptFollow(AcceptFollow),
UndoFollow(UndoFollow),
CreateOrUpdatePrivateMessage(CreateOrUpdateChatMessage),
Report(Report),
AnnounceActivity(AnnounceActivity),
//AnnouncableActivities(AnnouncableActivities),
/// This is a catch-all and needs to be last
RawAnnouncableActivities(RawAnnouncableActivities),
}

/// List of activities which the group inbox can handle.
#[derive(Debug, Deserialize, Serialize)]
#[serde(untagged)]
#[enum_delegate::implement(ActivityHandler)]
pub enum GroupInboxActivities {
Follow(Follow),
UndoFollow(UndoFollow),
Report(Report),
// This is a catch-all and needs to be last
/// This is a catch-all and needs to be last
AnnouncableActivities(RawAnnouncableActivities),
}

/// List of activities which the person inbox can handle.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(untagged)]
#[enum_delegate::implement(ActivityHandler)]
Expand All @@ -64,17 +74,8 @@ pub enum PersonInboxActivities {
Delete(Delete),
UndoDelete(UndoDelete),
AnnounceActivity(AnnounceActivity),
}

/// This is necessary for user inbox, which can also receive some "announcable" activities,
/// eg a comment mention. This needs to be a separate enum so that announcables received in shared
/// inbox can fall through to be parsed as GroupInboxActivities::AnnouncableActivities.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(untagged)]
#[enum_delegate::implement(ActivityHandler)]
pub enum PersonInboxActivitiesWithAnnouncable {
PersonInboxActivities(Box<PersonInboxActivities>),
AnnouncableActivities(Box<AnnouncableActivities>),
/// User can also receive some "announcable" activities, eg a comment mention.
AnnouncableActivities(AnnouncableActivities),
}

#[derive(Clone, Debug, Deserialize, Serialize)]
Expand Down Expand Up @@ -138,12 +139,7 @@ mod tests {
#![allow(clippy::indexing_slicing)]

use crate::{
activity_lists::{
GroupInboxActivities,
PersonInboxActivities,
PersonInboxActivitiesWithAnnouncable,
SiteInboxActivities,
},
activity_lists::{GroupInboxActivities, PersonInboxActivities, SiteInboxActivities},
protocol::tests::{test_json, test_parse_lemmy_item},
};

Expand All @@ -161,16 +157,15 @@ mod tests {
fn test_person_inbox() {
test_parse_lemmy_item::<PersonInboxActivities>("assets/lemmy/activities/following/accept.json")
.unwrap();
test_parse_lemmy_item::<PersonInboxActivitiesWithAnnouncable>(
test_parse_lemmy_item::<PersonInboxActivities>(
"assets/lemmy/activities/create_or_update/create_note.json",
)
.unwrap();
test_parse_lemmy_item::<PersonInboxActivitiesWithAnnouncable>(
test_parse_lemmy_item::<PersonInboxActivities>(
"assets/lemmy/activities/create_or_update/create_private_message.json",
)
.unwrap();
test_json::<PersonInboxActivitiesWithAnnouncable>("assets/mastodon/activities/follow.json")
.unwrap();
test_json::<PersonInboxActivities>("assets/mastodon/activities/follow.json").unwrap();
}

#[test]
Expand Down
4 changes: 2 additions & 2 deletions crates/apub/src/http/person.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{
activity_lists::PersonInboxActivitiesWithAnnouncable,
activity_lists::PersonInboxActivities,
fetcher::user_or_community::UserOrCommunity,
http::{create_apub_response, create_apub_tombstone_response},
objects::person::ApubPerson,
Expand Down Expand Up @@ -49,7 +49,7 @@ pub async fn person_inbox(
body: Bytes,
data: Data<LemmyContext>,
) -> Result<HttpResponse, LemmyError> {
receive_activity::<WithContext<PersonInboxActivitiesWithAnnouncable>, UserOrCommunity, LemmyContext>(
receive_activity::<WithContext<PersonInboxActivities>, UserOrCommunity, LemmyContext>(
request, body, &data,
)
.await
Expand Down