1 jest.setTimeout(180000);
3 import { PostResponse } from "lemmy-js-client/dist/types/PostResponse";
36 import { CommentView } from "lemmy-js-client/dist/types/CommentView";
38 let postOnAlphaRes: PostResponse;
40 beforeAll(async () => {
43 await followBeta(alpha);
44 await followBeta(gamma);
45 let betaCommunity = (await resolveBetaCommunity(alpha)).community;
47 postOnAlphaRes = await createPost(alpha, betaCommunity.community.id);
51 afterAll(async () => {
55 function assertCommentFederation(
56 commentOne?: CommentView,
57 commentTwo?: CommentView,
59 expect(commentOne?.comment.ap_id).toBe(commentTwo?.comment.ap_id);
60 expect(commentOne?.comment.content).toBe(commentTwo?.comment.content);
61 expect(commentOne?.creator.name).toBe(commentTwo?.creator.name);
62 expect(commentOne?.community.actor_id).toBe(commentTwo?.community.actor_id);
63 expect(commentOne?.comment.published).toBe(commentTwo?.comment.published);
64 expect(commentOne?.comment.updated).toBe(commentOne?.comment.updated);
65 expect(commentOne?.comment.deleted).toBe(commentOne?.comment.deleted);
66 expect(commentOne?.comment.removed).toBe(commentOne?.comment.removed);
69 test("Create a comment", async () => {
70 let commentRes = await createComment(alpha, postOnAlphaRes.post_view.post.id);
71 expect(commentRes.comment_view.comment.content).toBeDefined();
72 expect(commentRes.comment_view.community.local).toBe(false);
73 expect(commentRes.comment_view.creator.local).toBe(true);
74 expect(commentRes.comment_view.counts.score).toBe(1);
76 // Make sure that comment is liked on beta
78 await resolveComment(beta, commentRes.comment_view.comment)
80 expect(betaComment).toBeDefined();
81 expect(betaComment?.community.local).toBe(true);
82 expect(betaComment?.creator.local).toBe(false);
83 expect(betaComment?.counts.score).toBe(1);
84 assertCommentFederation(betaComment, commentRes.comment_view);
87 test("Create a comment in a non-existent post", async () => {
88 await expect(createComment(alpha, -1)).rejects.toBe("couldnt_find_post");
91 test("Update a comment", async () => {
92 let commentRes = await createComment(alpha, postOnAlphaRes.post_view.post.id);
93 // Federate the comment first
95 await resolveComment(beta, commentRes.comment_view.comment)
97 assertCommentFederation(betaComment, commentRes.comment_view);
99 let updateCommentRes = await editComment(
101 commentRes.comment_view.comment.id,
103 expect(updateCommentRes.comment_view.comment.content).toBe(
104 "A jest test federated comment update",
106 expect(updateCommentRes.comment_view.community.local).toBe(false);
107 expect(updateCommentRes.comment_view.creator.local).toBe(true);
109 // Make sure that post is updated on beta
110 let betaCommentUpdated = (
111 await resolveComment(beta, commentRes.comment_view.comment)
113 assertCommentFederation(betaCommentUpdated, updateCommentRes.comment_view);
116 test("Delete a comment", async () => {
117 // creating a comment on alpha (remote from home of community)
118 let commentRes = await createComment(alpha, postOnAlphaRes.post_view.post.id);
120 // Find the comment on beta (home of community)
122 await resolveComment(beta, commentRes.comment_view.comment)
126 throw "Missing beta comment before delete";
129 // Find the comment on remote instance gamma
131 await resolveComment(gamma, commentRes.comment_view.comment)
135 throw "Missing gamma comment (remote-home-remote replication) before delete";
138 let deleteCommentRes = await deleteComment(
141 commentRes.comment_view.comment.id,
143 expect(deleteCommentRes.comment_view.comment.deleted).toBe(true);
145 // Make sure that comment is undefined on beta
147 resolveComment(beta, commentRes.comment_view.comment),
148 ).rejects.toBe("couldnt_find_object");
150 // Make sure that comment is undefined on gamma after delete
152 resolveComment(gamma, commentRes.comment_view.comment),
153 ).rejects.toBe("couldnt_find_object");
155 // Test undeleting the comment
156 let undeleteCommentRes = await deleteComment(
159 commentRes.comment_view.comment.id,
161 expect(undeleteCommentRes.comment_view.comment.deleted).toBe(false);
163 // Make sure that comment is undeleted on beta
165 await resolveComment(beta, commentRes.comment_view.comment)
167 expect(betaComment2?.comment.deleted).toBe(false);
168 assertCommentFederation(betaComment2, undeleteCommentRes.comment_view);
171 test.skip("Remove a comment from admin and community on the same instance", async () => {
172 let commentRes = await createComment(alpha, postOnAlphaRes.post_view.post.id);
174 // Get the id for beta
175 let betaCommentId = (
176 await resolveComment(beta, commentRes.comment_view.comment)
177 ).comment?.comment.id;
179 if (!betaCommentId) {
180 throw "beta comment id is missing";
183 // The beta admin removes it (the community lives on beta)
184 let removeCommentRes = await removeComment(beta, true, betaCommentId);
185 expect(removeCommentRes.comment_view.comment.removed).toBe(true);
187 // Make sure that comment is removed on alpha (it gets pushed since an admin from beta removed it)
188 let refetchedPostComments = await getPersonDetails(
190 commentRes.comment_view.comment.creator_id,
192 expect(refetchedPostComments.comments[0].comment.removed).toBe(true);
194 // beta will unremove the comment
195 let unremoveCommentRes = await removeComment(beta, false, betaCommentId);
196 expect(unremoveCommentRes.comment_view.comment.removed).toBe(false);
198 // Make sure that comment is unremoved on alpha
199 let refetchedPostComments2 = await getComments(
201 postOnAlphaRes.post_view.post.id,
203 expect(refetchedPostComments2.comments[0].comment.removed).toBe(false);
204 assertCommentFederation(
205 refetchedPostComments2.comments[0],
206 unremoveCommentRes.comment_view,
210 test("Remove a comment from admin and community on different instance", async () => {
211 let alpha_user = await registerUser(alpha);
212 let newAlphaApi: API = {
213 client: alpha.client,
214 auth: alpha_user.jwt ?? "",
217 // New alpha user creates a community, post, and comment.
218 let newCommunity = await createCommunity(newAlphaApi);
219 let newPost = await createPost(
221 newCommunity.community_view.community.id,
223 let commentRes = await createComment(newAlphaApi, newPost.post_view.post.id);
224 expect(commentRes.comment_view.comment.content).toBeDefined();
226 // Beta searches that to cache it, then removes it
228 await resolveComment(beta, commentRes.comment_view.comment)
232 throw "beta comment missing";
235 let removeCommentRes = await removeComment(
238 betaComment.comment.id,
240 expect(removeCommentRes.comment_view.comment.removed).toBe(true);
242 // Make sure its not removed on alpha
243 let refetchedPostComments = await getComments(
245 newPost.post_view.post.id,
247 expect(refetchedPostComments.comments[0].comment.removed).toBe(false);
248 assertCommentFederation(
249 refetchedPostComments.comments[0],
250 commentRes.comment_view,
254 test("Unlike a comment", async () => {
255 let commentRes = await createComment(alpha, postOnAlphaRes.post_view.post.id);
257 // Lemmy automatically creates 1 like (vote) by author of comment.
258 // Make sure that comment is liked (voted up) on gamma, downstream peer
259 // This is testing replication from remote-home-remote (alpha-beta-gamma)
260 let gammaComment1 = (
261 await resolveComment(gamma, commentRes.comment_view.comment)
263 expect(gammaComment1).toBeDefined();
264 expect(gammaComment1?.community.local).toBe(false);
265 expect(gammaComment1?.creator.local).toBe(false);
266 expect(gammaComment1?.counts.score).toBe(1);
268 let unlike = await likeComment(alpha, 0, commentRes.comment_view.comment);
269 expect(unlike.comment_view.counts.score).toBe(0);
271 // Make sure that comment is unliked on beta
273 await resolveComment(beta, commentRes.comment_view.comment)
275 expect(betaComment).toBeDefined();
276 expect(betaComment?.community.local).toBe(true);
277 expect(betaComment?.creator.local).toBe(false);
278 expect(betaComment?.counts.score).toBe(0);
280 // Make sure that comment is unliked on gamma, downstream peer
281 // This is testing replication from remote-home-remote (alpha-beta-gamma)
283 await resolveComment(gamma, commentRes.comment_view.comment)
285 expect(gammaComment).toBeDefined();
286 expect(gammaComment?.community.local).toBe(false);
287 expect(gammaComment?.creator.local).toBe(false);
288 expect(gammaComment?.counts.score).toBe(0);
291 test("Federated comment like", async () => {
292 let commentRes = await createComment(alpha, postOnAlphaRes.post_view.post.id);
294 // Find the comment on beta
296 await resolveComment(beta, commentRes.comment_view.comment)
300 throw "Missing beta comment";
303 let like = await likeComment(beta, 1, betaComment.comment);
304 expect(like.comment_view.counts.score).toBe(2);
306 // Get the post from alpha, check the likes
307 let postComments = await getComments(alpha, postOnAlphaRes.post_view.post.id);
308 expect(postComments.comments[0].counts.score).toBe(2);
311 test("Reply to a comment from another instance, get notification", async () => {
312 // Create a root-level trunk-branch comment on alpha
313 let commentRes = await createComment(alpha, postOnAlphaRes.post_view.post.id);
314 // find that comment id on beta
316 await resolveComment(beta, commentRes.comment_view.comment)
320 throw "Missing beta comment";
323 // Reply from beta, extending the branch
324 let replyRes = await createComment(
327 betaComment.comment.id,
329 expect(replyRes.comment_view.comment.content).toBeDefined();
330 expect(replyRes.comment_view.community.local).toBe(true);
331 expect(replyRes.comment_view.creator.local).toBe(true);
332 expect(getCommentParentId(replyRes.comment_view.comment)).toBe(
333 betaComment.comment.id,
335 expect(replyRes.comment_view.counts.score).toBe(1);
337 // Make sure that reply comment is seen on alpha
338 // TODO not sure why, but a searchComment back to alpha, for the ap_id of betas
339 // comment, isn't working.
340 // let searchAlpha = await searchComment(alpha, replyRes.comment);
341 let postComments = await getComments(alpha, postOnAlphaRes.post_view.post.id);
342 // Note: in Lemmy 0.18.3 pre-release this is coming up 7
343 expect(postComments.comments.length).toBeGreaterThanOrEqual(2);
344 let alphaComment = postComments.comments[0];
345 expect(alphaComment.comment.content).toBeDefined();
346 expect(getCommentParentId(alphaComment.comment)).toBe(
347 postComments.comments[1].comment.id,
349 expect(alphaComment.community.local).toBe(false);
350 expect(alphaComment.creator.local).toBe(false);
351 expect(alphaComment.counts.score).toBe(1);
352 assertCommentFederation(alphaComment, replyRes.comment_view);
354 // Did alpha get notified of the reply from beta?
355 let alphaUnreadCountRes = await getUnreadCount(alpha);
356 expect(alphaUnreadCountRes.replies).toBe(1);
358 // check inbox of replies on alpha, fetching read/unread both
359 let alphaRepliesRes = await getReplies(alpha);
360 expect(alphaRepliesRes.replies.length).toBe(1);
361 expect(alphaRepliesRes.replies[0].comment.content).toBeDefined();
362 expect(alphaRepliesRes.replies[0].community.local).toBe(false);
363 expect(alphaRepliesRes.replies[0].creator.local).toBe(false);
364 expect(alphaRepliesRes.replies[0].counts.score).toBe(1);
365 // ToDo: interesting alphaRepliesRes.replies[0].comment_reply.id is 1, meaning? how did that come about?
366 expect(alphaRepliesRes.replies[0].comment.id).toBe(alphaComment.comment.id);
367 // this is a new notification, getReplies fetch was for read/unread both, confirm it is unread.
368 expect(alphaRepliesRes.replies[0].comment_reply.read).toBe(false);
369 assertCommentFederation(alphaRepliesRes.replies[0], replyRes.comment_view);
372 test("Mention beta from alpha", async () => {
373 // Create a new branch, trunk-level comment branch, from alpha instance
374 let commentRes = await createComment(alpha, postOnAlphaRes.post_view.post.id);
375 // Create a reply comment to previous comment, this has a mention in body
376 let mentionContent = "A test mention of @lemmy_beta@lemmy-beta:8551";
377 let mentionRes = await createComment(
379 postOnAlphaRes.post_view.post.id,
380 commentRes.comment_view.comment.id,
383 expect(mentionRes.comment_view.comment.content).toBeDefined();
384 expect(mentionRes.comment_view.community.local).toBe(false);
385 expect(mentionRes.comment_view.creator.local).toBe(true);
386 expect(mentionRes.comment_view.counts.score).toBe(1);
388 // get beta's localized copy of the alpha post
389 let betaPost = (await resolvePost(beta, postOnAlphaRes.post_view.post)).post;
391 throw "unable to locate post on beta";
393 expect(betaPost.post.ap_id).toBe(postOnAlphaRes.post_view.post.ap_id);
394 expect(betaPost.post.name).toBe(postOnAlphaRes.post_view.post.name);
396 // Make sure that both new comments are seen on beta and have parent/child relationship
397 let betaPostComments = await getComments(beta, betaPost.post.id);
398 expect(betaPostComments.comments.length).toBeGreaterThanOrEqual(2);
399 // the trunk-branch root comment will be older than the mention reply comment, so index 1
400 let betaRootComment = betaPostComments.comments[1];
401 // the trunk-branch root comment should not have a parent
402 expect(getCommentParentId(betaRootComment.comment)).toBeUndefined();
403 expect(betaRootComment.comment.content).toBeDefined();
404 // the mention reply comment should have parent that points to the branch root level comment
405 expect(getCommentParentId(betaPostComments.comments[0].comment)).toBe(
406 betaPostComments.comments[1].comment.id,
408 expect(betaRootComment.community.local).toBe(true);
409 expect(betaRootComment.creator.local).toBe(false);
410 expect(betaRootComment.counts.score).toBe(1);
411 assertCommentFederation(betaRootComment, commentRes.comment_view);
413 let mentionsRes = await getMentions(beta);
414 expect(mentionsRes.mentions[0].comment.content).toBeDefined();
415 expect(mentionsRes.mentions[0].community.local).toBe(true);
416 expect(mentionsRes.mentions[0].creator.local).toBe(false);
417 expect(mentionsRes.mentions[0].counts.score).toBe(1);
418 // the reply comment with mention should be the most fresh, newest, index 0
419 expect(mentionsRes.mentions[0].person_mention.comment_id).toBe(
420 betaPostComments.comments[0].comment.id,
424 test("Comment Search", async () => {
425 let commentRes = await createComment(alpha, postOnAlphaRes.post_view.post.id);
427 await resolveComment(beta, commentRes.comment_view.comment)
429 assertCommentFederation(betaComment, commentRes.comment_view);
432 test("A and G subscribe to B (center) A posts, G mentions B, it gets announced to A", async () => {
433 // Create a local post
434 let alphaCommunity = (await resolveCommunity(alpha, "!main@lemmy-alpha:8541"))
437 if (!alphaCommunity) {
438 throw "Missing alpha community";
441 let alphaPost = await createPost(alpha, alphaCommunity.community.id);
442 expect(alphaPost.post_view.community.local).toBe(true);
444 // Make sure gamma sees it
445 let gammaPost = (await resolvePost(gamma, alphaPost.post_view.post)).post;
448 throw "Missing gamma post";
452 "A jest test federated comment announce, lets mention @lemmy_beta@lemmy-beta:8551";
453 let commentRes = await createComment(
459 expect(commentRes.comment_view.comment.content).toBe(commentContent);
460 expect(commentRes.comment_view.community.local).toBe(false);
461 expect(commentRes.comment_view.creator.local).toBe(true);
462 expect(commentRes.comment_view.counts.score).toBe(1);
464 // Make sure alpha sees it
465 let alphaPostComments2 = await getComments(
467 alphaPost.post_view.post.id,
469 expect(alphaPostComments2.comments[0].comment.content).toBe(commentContent);
470 expect(alphaPostComments2.comments[0].community.local).toBe(true);
471 expect(alphaPostComments2.comments[0].creator.local).toBe(false);
472 expect(alphaPostComments2.comments[0].counts.score).toBe(1);
473 assertCommentFederation(
474 alphaPostComments2.comments[0],
475 commentRes.comment_view,
478 // Make sure beta has mentions
479 let mentionsRes = await getMentions(beta);
480 expect(mentionsRes.mentions[0].comment.content).toBe(commentContent);
481 expect(mentionsRes.mentions[0].community.local).toBe(false);
482 expect(mentionsRes.mentions[0].creator.local).toBe(false);
483 // TODO this is failing because fetchInReplyTos aren't getting score
484 // expect(mentionsRes.mentions[0].score).toBe(1);
487 test("Check that activity from another instance is sent to third instance", async () => {
488 // Alpha and gamma users follow beta community
489 let alphaFollow = await followBeta(alpha);
490 expect(alphaFollow.community_view.community.local).toBe(false);
491 expect(alphaFollow.community_view.community.name).toBe("main");
493 let gammaFollow = await followBeta(gamma);
494 expect(gammaFollow.community_view.community.local).toBe(false);
495 expect(gammaFollow.community_view.community.name).toBe("main");
497 // Create a post on beta
498 let betaPost = await createPost(beta, 2);
499 expect(betaPost.post_view.community.local).toBe(true);
501 // Make sure gamma and alpha see it
502 let gammaPost = (await resolvePost(gamma, betaPost.post_view.post)).post;
504 throw "Missing gamma post";
506 expect(gammaPost.post).toBeDefined();
508 let alphaPost = (await resolvePost(alpha, betaPost.post_view.post)).post;
510 throw "Missing alpha post";
512 expect(alphaPost.post).toBeDefined();
514 // The bug: gamma comments, and alpha should see it.
515 let commentContent = "Comment from gamma";
516 let commentRes = await createComment(
522 expect(commentRes.comment_view.comment.content).toBe(commentContent);
523 expect(commentRes.comment_view.community.local).toBe(false);
524 expect(commentRes.comment_view.creator.local).toBe(true);
525 expect(commentRes.comment_view.counts.score).toBe(1);
527 // Make sure alpha sees it
528 let alphaPostComments2 = await getComments(alpha, alphaPost.post.id);
529 expect(alphaPostComments2.comments[0].comment.content).toBe(commentContent);
530 expect(alphaPostComments2.comments[0].community.local).toBe(false);
531 expect(alphaPostComments2.comments[0].creator.local).toBe(false);
532 expect(alphaPostComments2.comments[0].counts.score).toBe(1);
533 assertCommentFederation(
534 alphaPostComments2.comments[0],
535 commentRes.comment_view,
538 await unfollowRemotes(alpha);
539 await unfollowRemotes(gamma);
542 test("Fetch in_reply_tos: A is unsubbed from B, B makes a post, and some embedded comments, A subs to B, B updates the lowest level comment, A fetches both the post and all the inreplyto comments for that post.", async () => {
543 // Unfollow all remote communities
544 let site = await unfollowRemotes(alpha);
546 site.my_user?.follows.filter(c => c.community.local == false).length,
549 // B creates a post, and two comments, should be invisible to A
550 let postOnBetaRes = await createPost(beta, 2);
551 expect(postOnBetaRes.post_view.post.name).toBeDefined();
553 let parentCommentContent = "An invisible top level comment from beta";
554 let parentCommentRes = await createComment(
556 postOnBetaRes.post_view.post.id,
558 parentCommentContent,
560 expect(parentCommentRes.comment_view.comment.content).toBe(
561 parentCommentContent,
564 // B creates a comment, then a child one of that.
565 let childCommentContent = "An invisible child comment from beta";
566 let childCommentRes = await createComment(
568 postOnBetaRes.post_view.post.id,
569 parentCommentRes.comment_view.comment.id,
572 expect(childCommentRes.comment_view.comment.content).toBe(
577 let follow = await followBeta(alpha);
578 expect(follow.community_view.community.local).toBe(false);
579 expect(follow.community_view.community.name).toBe("main");
581 // An update to the child comment on beta, should push the post, parent, and child to alpha now
582 let updatedCommentContent = "An update child comment from beta";
583 let updateRes = await editComment(
585 childCommentRes.comment_view.comment.id,
586 updatedCommentContent,
588 expect(updateRes.comment_view.comment.content).toBe(updatedCommentContent);
590 // Get the post from alpha
591 let alphaPostB = (await resolvePost(alpha, postOnBetaRes.post_view.post))
594 throw "Missing alpha post B";
597 let alphaPost = await getPost(alpha, alphaPostB.post.id);
598 let alphaPostComments = await getComments(alpha, alphaPostB.post.id);
599 expect(alphaPost.post_view.post.name).toBeDefined();
600 assertCommentFederation(
601 alphaPostComments.comments[1],
602 parentCommentRes.comment_view,
604 assertCommentFederation(
605 alphaPostComments.comments[0],
606 updateRes.comment_view,
608 expect(alphaPost.post_view.community.local).toBe(false);
609 expect(alphaPost.post_view.creator.local).toBe(false);
611 await unfollowRemotes(alpha);
614 test("Report a comment", async () => {
615 let betaCommunity = (await resolveBetaCommunity(beta)).community;
616 if (!betaCommunity) {
617 throw "Missing beta community";
619 let postOnBetaRes = (await createPost(beta, betaCommunity.community.id))
621 expect(postOnBetaRes).toBeDefined();
622 let commentRes = (await createComment(beta, postOnBetaRes.id)).comment_view
624 expect(commentRes).toBeDefined();
626 let alphaComment = (await resolveComment(alpha, commentRes)).comment?.comment;
628 throw "Missing alpha comment";
632 await reportComment(alpha, alphaComment.id, randomString(10))
633 ).comment_report_view.comment_report;
635 let betaReport = (await listCommentReports(beta)).comment_reports[0]
637 expect(betaReport).toBeDefined();
638 expect(betaReport.resolved).toBe(false);
639 expect(betaReport.original_comment_text).toBe(
640 alphaReport.original_comment_text,
642 expect(betaReport.reason).toBe(alphaReport.reason);