Federation tests replication round1 - demonstrate absent replication of comment deletes (#3657)
* more robust test of unlike a comment, confirm replication to instance downstream from community home * more robust 'delete a comment' test, confirm replication * Far more robust "Report a comment" test. Many comments about situation, this is currently failing because gamma does not get the report * typo and actually have Gamma comment check use gamma, not alpha * prepare-drone-federation-test.sh has some more echo output and note about the LEMMY_DATABASE_URL format (#3651) * Add http cache for webfingers (#3317) * Add http cache for webfingers * Remove the outgoing cache middleware & adjust the cache headers directive * Use 1h & 3day cache header * Update routes and adjust the cache headers location * revert apub caching --------- Co-authored-by: Dessalines <dessalines@users.noreply.github.com> Co-authored-by: Felix Ableitner <me@nutomic.com> * Rewrite activity lists to fix delete federation (fixes #3625) * Revert "typo and actually have Gamma comment check use gamma, not alpha" This reverts commit 7dfb6ee0f4885da3a2d10316422f5b510772806c. * Revert "Far more robust "Report a comment" test. Many comments about situation, this is currently failing because gamma does not get the report" This reverts commit 7bd3b20ae08a64324029491ddb3ce4295ba16787. * prettier TypeScript * revised comments, as ResolveObject isn't using routine replication * fmt * fix api tests * remove comment --------- Co-authored-by: cetra3 <cetra3@hotmail.com> Co-authored-by: Dessalines <dessalines@users.noreply.github.com> Co-authored-by: Felix Ableitner <me@nutomic.com>
This commit is contained in:
parent
2d7b416652
commit
21a87ebaf2
5 changed files with 97 additions and 40 deletions
16
Cargo.lock
generated
16
Cargo.lock
generated
|
@ -392,7 +392,7 @@ version = "3.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "64e6d1c7838db705c9b756557ee27c384ce695a1c51a6fe528784cb1c6840170"
|
checksum = "64e6d1c7838db705c9b756557ee27c384ce695a1c51a6fe528784cb1c6840170"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"html5ever 0.26.0",
|
"html5ever",
|
||||||
"maplit",
|
"maplit",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"tendril",
|
"tendril",
|
||||||
|
@ -439,6 +439,19 @@ dependencies = [
|
||||||
"serde_json",
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-compression"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "62b74f44609f0f91493e3082d3734d98497e094777144380ea4db9f9905dd5b6"
|
||||||
|
dependencies = [
|
||||||
|
"flate2",
|
||||||
|
"futures-core",
|
||||||
|
"memchr",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-io"
|
name = "async-io"
|
||||||
version = "1.13.0"
|
version = "1.13.0"
|
||||||
|
@ -4176,6 +4189,7 @@ version = "0.11.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55"
|
checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"async-compression",
|
||||||
"base64 0.21.2",
|
"base64 0.21.2",
|
||||||
"bytes",
|
"bytes",
|
||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
|
|
|
@ -112,8 +112,27 @@ test("Update a comment", async () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Delete 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);
|
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;
|
||||||
|
|
||||||
|
if (!gammaComment) {
|
||||||
|
throw "Missing gamma comment (remote-home-remote replication) before delete";
|
||||||
|
}
|
||||||
|
|
||||||
let deleteCommentRes = await deleteComment(
|
let deleteCommentRes = await deleteComment(
|
||||||
alpha,
|
alpha,
|
||||||
true,
|
true,
|
||||||
|
@ -126,6 +145,12 @@ test("Delete a comment", async () => {
|
||||||
resolveComment(beta, commentRes.comment_view.comment),
|
resolveComment(beta, commentRes.comment_view.comment),
|
||||||
).rejects.toBe("couldnt_find_object");
|
).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(
|
let undeleteCommentRes = await deleteComment(
|
||||||
alpha,
|
alpha,
|
||||||
false,
|
false,
|
||||||
|
@ -225,10 +250,22 @@ test("Remove a comment from admin and community on different instance", async ()
|
||||||
|
|
||||||
test("Unlike a comment", async () => {
|
test("Unlike a comment", async () => {
|
||||||
let commentRes = await createComment(alpha, postRes.post_view.post.id);
|
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);
|
let unlike = await likeComment(alpha, 0, commentRes.comment_view.comment);
|
||||||
expect(unlike.comment_view.counts.score).toBe(0);
|
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 = (
|
let betaComment = (
|
||||||
await resolveComment(beta, commentRes.comment_view.comment)
|
await resolveComment(beta, commentRes.comment_view.comment)
|
||||||
).comment;
|
).comment;
|
||||||
|
@ -236,6 +273,16 @@ test("Unlike a comment", async () => {
|
||||||
expect(betaComment?.community.local).toBe(true);
|
expect(betaComment?.community.local).toBe(true);
|
||||||
expect(betaComment?.creator.local).toBe(false);
|
expect(betaComment?.creator.local).toBe(false);
|
||||||
expect(betaComment?.counts.score).toBe(0);
|
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 () => {
|
test("Federated comment like", async () => {
|
||||||
|
|
|
@ -50,17 +50,19 @@ impl ActivityHandler for RawAnnouncableActivities {
|
||||||
if let AnnouncableActivities::Page(_) = activity {
|
if let AnnouncableActivities::Page(_) = activity {
|
||||||
return Err(LemmyErrorType::CannotReceivePage)?;
|
return Err(LemmyErrorType::CannotReceivePage)?;
|
||||||
}
|
}
|
||||||
let community = activity.community(data).await?;
|
|
||||||
let actor_id = activity.actor().clone().into();
|
|
||||||
|
|
||||||
// verify and receive activity
|
// verify and receive activity
|
||||||
activity.verify(data).await?;
|
activity.verify(data).await?;
|
||||||
activity.receive(data).await?;
|
activity.clone().receive(data).await?;
|
||||||
|
|
||||||
// send to community followers
|
// if activity is in a community, send to followers
|
||||||
if community.local {
|
let community = activity.community(data).await;
|
||||||
verify_person_in_community(&actor_id, &community, data).await?;
|
if let Ok(community) = community {
|
||||||
AnnounceActivity::send(self, &community, data).await?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,24 +24,32 @@ use crate::{
|
||||||
InCommunity,
|
InCommunity,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use activitypub_federation::{
|
use activitypub_federation::{config::Data, traits::ActivityHandler};
|
||||||
config::Data,
|
|
||||||
protocol::context::WithContext,
|
|
||||||
traits::ActivityHandler,
|
|
||||||
};
|
|
||||||
use lemmy_api_common::context::LemmyContext;
|
use lemmy_api_common::context::LemmyContext;
|
||||||
use lemmy_utils::error::LemmyError;
|
use lemmy_utils::error::LemmyError;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use url::Url;
|
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)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
#[enum_delegate::implement(ActivityHandler)]
|
#[enum_delegate::implement(ActivityHandler)]
|
||||||
pub enum SharedInboxActivities {
|
pub enum SharedInboxActivities {
|
||||||
PersonInboxActivities(Box<WithContext<PersonInboxActivities>>),
|
Follow(Follow),
|
||||||
GroupInboxActivities(Box<WithContext<GroupInboxActivities>>),
|
AcceptFollow(AcceptFollow),
|
||||||
|
UndoFollow(UndoFollow),
|
||||||
|
CreateOrUpdatePrivateMessage(CreateOrUpdateChatMessage),
|
||||||
|
Report(Report),
|
||||||
|
AnnounceActivity(AnnounceActivity),
|
||||||
|
/// 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)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
#[enum_delegate::implement(ActivityHandler)]
|
#[enum_delegate::implement(ActivityHandler)]
|
||||||
|
@ -49,10 +57,11 @@ pub enum GroupInboxActivities {
|
||||||
Follow(Follow),
|
Follow(Follow),
|
||||||
UndoFollow(UndoFollow),
|
UndoFollow(UndoFollow),
|
||||||
Report(Report),
|
Report(Report),
|
||||||
// This is a catch-all and needs to be last
|
/// This is a catch-all and needs to be last
|
||||||
AnnouncableActivities(RawAnnouncableActivities),
|
AnnouncableActivities(RawAnnouncableActivities),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// List of activities which the person inbox can handle.
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
#[enum_delegate::implement(ActivityHandler)]
|
#[enum_delegate::implement(ActivityHandler)]
|
||||||
|
@ -64,17 +73,8 @@ pub enum PersonInboxActivities {
|
||||||
Delete(Delete),
|
Delete(Delete),
|
||||||
UndoDelete(UndoDelete),
|
UndoDelete(UndoDelete),
|
||||||
AnnounceActivity(AnnounceActivity),
|
AnnounceActivity(AnnounceActivity),
|
||||||
}
|
/// User can also receive some "announcable" activities, eg a comment mention.
|
||||||
|
AnnouncableActivities(AnnouncableActivities),
|
||||||
/// 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>),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
@ -138,12 +138,7 @@ mod tests {
|
||||||
#![allow(clippy::indexing_slicing)]
|
#![allow(clippy::indexing_slicing)]
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
activity_lists::{
|
activity_lists::{GroupInboxActivities, PersonInboxActivities, SiteInboxActivities},
|
||||||
GroupInboxActivities,
|
|
||||||
PersonInboxActivities,
|
|
||||||
PersonInboxActivitiesWithAnnouncable,
|
|
||||||
SiteInboxActivities,
|
|
||||||
},
|
|
||||||
protocol::tests::{test_json, test_parse_lemmy_item},
|
protocol::tests::{test_json, test_parse_lemmy_item},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -161,16 +156,15 @@ mod tests {
|
||||||
fn test_person_inbox() {
|
fn test_person_inbox() {
|
||||||
test_parse_lemmy_item::<PersonInboxActivities>("assets/lemmy/activities/following/accept.json")
|
test_parse_lemmy_item::<PersonInboxActivities>("assets/lemmy/activities/following/accept.json")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
test_parse_lemmy_item::<PersonInboxActivitiesWithAnnouncable>(
|
test_parse_lemmy_item::<PersonInboxActivities>(
|
||||||
"assets/lemmy/activities/create_or_update/create_note.json",
|
"assets/lemmy/activities/create_or_update/create_note.json",
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
test_parse_lemmy_item::<PersonInboxActivitiesWithAnnouncable>(
|
test_parse_lemmy_item::<PersonInboxActivities>(
|
||||||
"assets/lemmy/activities/create_or_update/create_private_message.json",
|
"assets/lemmy/activities/create_or_update/create_private_message.json",
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
test_json::<PersonInboxActivitiesWithAnnouncable>("assets/mastodon/activities/follow.json")
|
test_json::<PersonInboxActivities>("assets/mastodon/activities/follow.json").unwrap();
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
activity_lists::PersonInboxActivitiesWithAnnouncable,
|
activity_lists::PersonInboxActivities,
|
||||||
fetcher::user_or_community::UserOrCommunity,
|
fetcher::user_or_community::UserOrCommunity,
|
||||||
http::{create_apub_response, create_apub_tombstone_response},
|
http::{create_apub_response, create_apub_tombstone_response},
|
||||||
objects::person::ApubPerson,
|
objects::person::ApubPerson,
|
||||||
|
@ -49,7 +49,7 @@ pub async fn person_inbox(
|
||||||
body: Bytes,
|
body: Bytes,
|
||||||
data: Data<LemmyContext>,
|
data: Data<LemmyContext>,
|
||||||
) -> Result<HttpResponse, LemmyError> {
|
) -> Result<HttpResponse, LemmyError> {
|
||||||
receive_activity::<WithContext<PersonInboxActivitiesWithAnnouncable>, UserOrCommunity, LemmyContext>(
|
receive_activity::<WithContext<PersonInboxActivities>, UserOrCommunity, LemmyContext>(
|
||||||
request, body, &data,
|
request, body, &data,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
Loading…
Reference in a new issue