diff --git a/api_tests/package.json b/api_tests/package.json index ec692e1b5..1810b8d2f 100644 --- a/api_tests/package.json +++ b/api_tests/package.json @@ -9,7 +9,7 @@ "scripts": { "lint": "tsc --noEmit && eslint --report-unused-disable-directives --ext .js,.ts,.tsx src && prettier --check 'src/**/*.ts'", "fix": "prettier --write src && eslint --fix src", - "api-test": "jest -i follow.spec.ts && jest -i src/post.spec.ts && jest -i comment.spec.ts && jest -i private_message.spec.ts && jest -i user.spec.ts && jest -i community.spec.ts" + "api-test": "jest -i follow.spec.ts && jest -i post.spec.ts && jest -i comment.spec.ts && jest -i private_message.spec.ts && jest -i user.spec.ts && jest -i community.spec.ts" }, "devDependencies": { "@types/jest": "^29.5.1", diff --git a/api_tests/prepare-drone-federation-test.sh b/api_tests/prepare-drone-federation-test.sh index 7eceeeb77..3aae16bda 100755 --- a/api_tests/prepare-drone-federation-test.sh +++ b/api_tests/prepare-drone-federation-test.sh @@ -31,7 +31,7 @@ else fi echo "killall existing lemmy_server processes" -killall lemmy_server || true +killall -s1 lemmy_server || true echo "$PWD" diff --git a/api_tests/run-federation-test.sh b/api_tests/run-federation-test.sh index 0d241e2ab..f611cce65 100755 --- a/api_tests/run-federation-test.sh +++ b/api_tests/run-federation-test.sh @@ -13,8 +13,6 @@ popd yarn yarn api-test || true -killall -s1 lemmy_server - for INSTANCE in lemmy_alpha lemmy_beta lemmy_gamma lemmy_delta lemmy_epsilon; do psql "$LEMMY_DATABASE_URL" -c "DROP DATABASE $INSTANCE" done diff --git a/api_tests/src/comment.spec.ts b/api_tests/src/comment.spec.ts index d7d533119..246497617 100644 --- a/api_tests/src/comment.spec.ts +++ b/api_tests/src/comment.spec.ts @@ -30,10 +30,12 @@ import { getCommentParentId, resolveCommunity, getPersonDetails, + getReplies, + getUnreadCount, } from "./shared"; import { CommentView } from "lemmy-js-client/dist/types/CommentView"; -let postRes: PostResponse; +let postOnAlphaRes: PostResponse; beforeAll(async () => { await setupLogins(); @@ -42,7 +44,7 @@ beforeAll(async () => { await followBeta(gamma); let betaCommunity = (await resolveBetaCommunity(alpha)).community; if (betaCommunity) { - postRes = await createPost(alpha, betaCommunity.community.id); + postOnAlphaRes = await createPost(alpha, betaCommunity.community.id); } }); @@ -65,7 +67,7 @@ function assertCommentFederation( } test("Create a comment", async () => { - let commentRes = await createComment(alpha, postRes.post_view.post.id); + let commentRes = await createComment(alpha, postOnAlphaRes.post_view.post.id); expect(commentRes.comment_view.comment.content).toBeDefined(); expect(commentRes.comment_view.community.local).toBe(false); expect(commentRes.comment_view.creator.local).toBe(true); @@ -87,7 +89,7 @@ test("Create a comment in a non-existent post", async () => { }); test("Update a comment", async () => { - let commentRes = await createComment(alpha, postRes.post_view.post.id); + let commentRes = await createComment(alpha, postOnAlphaRes.post_view.post.id); // Federate the comment first let betaComment = ( await resolveComment(beta, commentRes.comment_view.comment) @@ -113,7 +115,7 @@ 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); + let commentRes = await createComment(alpha, postOnAlphaRes.post_view.post.id); // Find the comment on beta (home of community) let betaComment = ( @@ -167,7 +169,7 @@ test("Delete a comment", async () => { }); test.skip("Remove a comment from admin and community on the same instance", async () => { - let commentRes = await createComment(alpha, postRes.post_view.post.id); + let commentRes = await createComment(alpha, postOnAlphaRes.post_view.post.id); // Get the id for beta let betaCommentId = ( @@ -189,13 +191,14 @@ test.skip("Remove a comment from admin and community on the same instance", asyn ); expect(refetchedPostComments.comments[0].comment.removed).toBe(true); + // beta will unremove the comment let unremoveCommentRes = await removeComment(beta, false, betaCommentId); expect(unremoveCommentRes.comment_view.comment.removed).toBe(false); - // Make sure that comment is unremoved on beta + // Make sure that comment is unremoved on alpha let refetchedPostComments2 = await getComments( alpha, - postRes.post_view.post.id, + postOnAlphaRes.post_view.post.id, ); expect(refetchedPostComments2.comments[0].comment.removed).toBe(false); assertCommentFederation( @@ -249,7 +252,7 @@ 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); + let commentRes = await createComment(alpha, postOnAlphaRes.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 @@ -286,7 +289,7 @@ test("Unlike a comment", async () => { }); test("Federated comment like", async () => { - let commentRes = await createComment(alpha, postRes.post_view.post.id); + let commentRes = await createComment(alpha, postOnAlphaRes.post_view.post.id); // Find the comment on beta let betaComment = ( @@ -301,13 +304,14 @@ test("Federated comment like", async () => { expect(like.comment_view.counts.score).toBe(2); // Get the post from alpha, check the likes - let postComments = await getComments(alpha, postRes.post_view.post.id); + let postComments = await getComments(alpha, postOnAlphaRes.post_view.post.id); expect(postComments.comments[0].counts.score).toBe(2); }); -test("Reply to a comment", async () => { - // Create a comment on alpha, find it on beta - let commentRes = await createComment(alpha, postRes.post_view.post.id); +test("Reply to a comment from another instance, get notification", async () => { + // Create a root-level trunk-branch comment on alpha + let commentRes = await createComment(alpha, postOnAlphaRes.post_view.post.id); + // find that comment id on beta let betaComment = ( await resolveComment(beta, commentRes.comment_view.comment) ).comment; @@ -316,9 +320,7 @@ test("Reply to a comment", async () => { throw "Missing beta comment"; } - // find that comment id on beta - - // Reply from beta + // Reply from beta, extending the branch let replyRes = await createComment( beta, betaComment.post.id, @@ -332,11 +334,13 @@ test("Reply to a comment", async () => { ); expect(replyRes.comment_view.counts.score).toBe(1); - // Make sure that comment is seen on alpha + // Make sure that reply comment is seen on alpha // TODO not sure why, but a searchComment back to alpha, for the ap_id of betas // comment, isn't working. // let searchAlpha = await searchComment(alpha, replyRes.comment); - let postComments = await getComments(alpha, postRes.post_view.post.id); + let postComments = await getComments(alpha, postOnAlphaRes.post_view.post.id); + // Note: in Lemmy 0.18.3 pre-release this is coming up 7 + expect(postComments.comments.length).toBeGreaterThanOrEqual(2); let alphaComment = postComments.comments[0]; expect(alphaComment.comment.content).toBeDefined(); expect(getCommentParentId(alphaComment.comment)).toBe( @@ -346,15 +350,33 @@ test("Reply to a comment", async () => { expect(alphaComment.creator.local).toBe(false); expect(alphaComment.counts.score).toBe(1); assertCommentFederation(alphaComment, replyRes.comment_view); + + // Did alpha get notified of the reply from beta? + let alphaUnreadCountRes = await getUnreadCount(alpha); + expect(alphaUnreadCountRes.replies).toBe(1); + + // check inbox of replies on alpha, fetching read/unread both + let alphaRepliesRes = await getReplies(alpha); + expect(alphaRepliesRes.replies.length).toBe(1); + expect(alphaRepliesRes.replies[0].comment.content).toBeDefined(); + expect(alphaRepliesRes.replies[0].community.local).toBe(false); + expect(alphaRepliesRes.replies[0].creator.local).toBe(false); + expect(alphaRepliesRes.replies[0].counts.score).toBe(1); + // ToDo: interesting alphaRepliesRes.replies[0].comment_reply.id is 1, meaning? how did that come about? + expect(alphaRepliesRes.replies[0].comment.id).toBe(alphaComment.comment.id); + // this is a new notification, getReplies fetch was for read/unread both, confirm it is unread. + expect(alphaRepliesRes.replies[0].comment_reply.read).toBe(false); + assertCommentFederation(alphaRepliesRes.replies[0], replyRes.comment_view); }); -test("Mention beta", async () => { - // Create a mention on alpha +test("Mention beta from alpha", async () => { + // Create a new branch, trunk-level comment branch, from alpha instance + let commentRes = await createComment(alpha, postOnAlphaRes.post_view.post.id); + // Create a reply comment to previous comment, this has a mention in body let mentionContent = "A test mention of @lemmy_beta@lemmy-beta:8551"; - let commentRes = await createComment(alpha, postRes.post_view.post.id); let mentionRes = await createComment( alpha, - postRes.post_view.post.id, + postOnAlphaRes.post_view.post.id, commentRes.comment_view.comment.id, mentionContent, ); @@ -363,15 +385,44 @@ test("Mention beta", async () => { expect(mentionRes.comment_view.creator.local).toBe(true); expect(mentionRes.comment_view.counts.score).toBe(1); + // get beta's localized copy of the alpha post + let betaPost = (await resolvePost(beta, postOnAlphaRes.post_view.post)).post; + if (!betaPost) { + throw "unable to locate post on beta"; + } + expect(betaPost.post.ap_id).toBe(postOnAlphaRes.post_view.post.ap_id); + expect(betaPost.post.name).toBe(postOnAlphaRes.post_view.post.name); + + // Make sure that both new comments are seen on beta and have parent/child relationship + let betaPostComments = await getComments(beta, betaPost.post.id); + expect(betaPostComments.comments.length).toBeGreaterThanOrEqual(2); + // the trunk-branch root comment will be older than the mention reply comment, so index 1 + let betaRootComment = betaPostComments.comments[1]; + // the trunk-branch root comment should not have a parent + expect(getCommentParentId(betaRootComment.comment)).toBeUndefined(); + expect(betaRootComment.comment.content).toBeDefined(); + // the mention reply comment should have parent that points to the branch root level comment + expect(getCommentParentId(betaPostComments.comments[0].comment)).toBe( + betaPostComments.comments[1].comment.id, + ); + expect(betaRootComment.community.local).toBe(true); + expect(betaRootComment.creator.local).toBe(false); + expect(betaRootComment.counts.score).toBe(1); + assertCommentFederation(betaRootComment, commentRes.comment_view); + let mentionsRes = await getMentions(beta); expect(mentionsRes.mentions[0].comment.content).toBeDefined(); expect(mentionsRes.mentions[0].community.local).toBe(true); expect(mentionsRes.mentions[0].creator.local).toBe(false); expect(mentionsRes.mentions[0].counts.score).toBe(1); + // the reply comment with mention should be the most fresh, newest, index 0 + expect(mentionsRes.mentions[0].person_mention.comment_id).toBe( + betaPostComments.comments[0].comment.id, + ); }); test("Comment Search", async () => { - let commentRes = await createComment(alpha, postRes.post_view.post.id); + let commentRes = await createComment(alpha, postOnAlphaRes.post_view.post.id); let betaComment = ( await resolveComment(beta, commentRes.comment_view.comment) ).comment; @@ -496,13 +547,13 @@ test("Fetch in_reply_tos: A is unsubbed from B, B makes a post, and some embedde ).toBe(0); // B creates a post, and two comments, should be invisible to A - let postRes = await createPost(beta, 2); - expect(postRes.post_view.post.name).toBeDefined(); + let postOnBetaRes = await createPost(beta, 2); + expect(postOnBetaRes.post_view.post.name).toBeDefined(); let parentCommentContent = "An invisible top level comment from beta"; let parentCommentRes = await createComment( beta, - postRes.post_view.post.id, + postOnBetaRes.post_view.post.id, undefined, parentCommentContent, ); @@ -514,7 +565,7 @@ test("Fetch in_reply_tos: A is unsubbed from B, B makes a post, and some embedde let childCommentContent = "An invisible child comment from beta"; let childCommentRes = await createComment( beta, - postRes.post_view.post.id, + postOnBetaRes.post_view.post.id, parentCommentRes.comment_view.comment.id, childCommentContent, ); @@ -537,7 +588,8 @@ test("Fetch in_reply_tos: A is unsubbed from B, B makes a post, and some embedde expect(updateRes.comment_view.comment.content).toBe(updatedCommentContent); // Get the post from alpha - let alphaPostB = (await resolvePost(alpha, postRes.post_view.post)).post; + let alphaPostB = (await resolvePost(alpha, postOnBetaRes.post_view.post)) + .post; if (!alphaPostB) { throw "Missing alpha post B"; } @@ -564,10 +616,11 @@ test("Report a comment", async () => { if (!betaCommunity) { throw "Missing beta community"; } - let postRes = (await createPost(beta, betaCommunity.community.id)).post_view - .post; - expect(postRes).toBeDefined(); - let commentRes = (await createComment(beta, postRes.id)).comment_view.comment; + let postOnBetaRes = (await createPost(beta, betaCommunity.community.id)) + .post_view.post; + expect(postOnBetaRes).toBeDefined(); + let commentRes = (await createComment(beta, postOnBetaRes.id)).comment_view + .comment; expect(commentRes).toBeDefined(); let alphaComment = (await resolveComment(alpha, commentRes)).comment?.comment; diff --git a/api_tests/src/shared.ts b/api_tests/src/shared.ts index f873e78c1..9460d896a 100644 --- a/api_tests/src/shared.ts +++ b/api_tests/src/shared.ts @@ -1,4 +1,11 @@ -import { LemmyHttp } from "lemmy-js-client"; +import { + GetReplies, + GetRepliesResponse, + GetUnreadCount, + GetUnreadCountResponse, + LemmyHttp, + LocalUser, +} from "lemmy-js-client"; import { CreatePost } from "lemmy-js-client/dist/types/CreatePost"; import { DeletePost } from "lemmy-js-client/dist/types/DeletePost"; import { EditPost } from "lemmy-js-client/dist/types/EditPost"; @@ -325,6 +332,24 @@ export async function getComments( return api.client.getComments(form); } +export async function getUnreadCount( + api: API, +): Promise { + let form: GetUnreadCount = { + auth: api.auth, + }; + return api.client.getUnreadCount(form); +} + +export async function getReplies(api: API): Promise { + let form: GetReplies = { + sort: "New", + unread_only: false, + auth: api.auth, + }; + return api.client.getReplies(form); +} + export async function resolveComment( api: API, comment: Comment,