import { count, createUser, db, del, get, insertComment, insertDump, patch, post, test, } from "../helpers.ts"; import { assertEquals, assertExists, assertFalse } from "@std/assert"; import { castCommentLike, removeCommentLike, } from "../../services/comment-like-service.ts"; import { APIException } from "../../lib/api-exception.ts"; import type { Comment } from "../../../shared/comment.ts"; import type { Dump } from "../../../shared/dump.ts"; import type { PaginatedData } from "../../../shared/api.ts"; import type { PublicUser } from "../../../shared/user.ts"; test("comments: post, list in order, count on the dump", async () => { const owner = await createUser(); const other = await createUser(); const { id } = insertDump({ userId: owner.id }); const first = await post(`/api/dumps/${id}/comments`, { auth: other.auth, json: { body: " first " }, }); assertEquals(first.status, 201); assertEquals(first.data.body, "first"); assertEquals(first.data.authorUsername, other.username); const reply = await post(`/api/dumps/${id}/comments`, { auth: owner.auth, json: { body: "reply", parentId: first.data.id }, }); assertEquals(reply.data.parentId, first.data.id); const list = await get(`/api/dumps/${id}/comments`); assertEquals(list.data.map((c) => c.body), [ "first", "reply", ]); assertEquals((await get(`/api/dumps/${id}`)).data.commentCount, 2); }); test("comments: validation, auth and unknown dumps", async () => { const user = await createUser(); const { id } = insertDump({ userId: user.id }); for (const body of ["", " ", "x".repeat(5001)]) { assertEquals( (await post(`/api/dumps/${id}/comments`, { auth: user.auth, json: { body }, })) .status, 400, ); } assertEquals( (await post(`/api/dumps/${id}/comments`, { json: { body: "hi" } })).status, 401, ); assertEquals( (await post(`/api/dumps/nope/comments`, { auth: user.auth, json: { body: "hi" }, })) .status, 404, ); }); test("comments on a private dump: owner only", async () => { const owner = await createUser(); const other = await createUser(); const { id } = insertDump({ userId: owner.id, isPrivate: true }); assertEquals( (await post(`/api/dumps/${id}/comments`, { auth: other.auth, json: { body: "x" }, })) .status, 404, ); assertEquals( (await post(`/api/dumps/${id}/comments`, { auth: owner.auth, json: { body: "x" }, })) .status, 201, ); assertEquals( (await get(`/api/dumps/${id}/comments`, { auth: other.auth })).status, 404, ); }); test("comments: the dump's owner is notified, the commenter isn't", async () => { const owner = await createUser(); const other = await createUser(); const { id } = insertDump({ userId: owner.id, title: "Mine" }); await post(`/api/dumps/${id}/comments`, { auth: owner.auth, json: { body: "self" }, }); assertEquals(count(`SELECT COUNT(*) as n FROM notifications;`), 0); const c = await post(`/api/dumps/${id}/comments`, { auth: other.auth, json: { body: "nice" }, }); const note = db.prepare(`SELECT user_id, type, data FROM notifications;`) .get() as { user_id: string; type: string; data: string; }; assertEquals(note.user_id, owner.id); assertEquals(note.type, "dump_commented"); assertEquals(JSON.parse(note.data).commentId, c.data.id); assertEquals(JSON.parse(note.data).dumpTitle, "Mine"); }); test("PATCH /api/comments/:id: author and moderator edit, others can't", async () => { const author = await createUser(); const other = await createUser(); const mod = await createUser({ role: "moderator" }); const { id: dumpId } = insertDump({ userId: author.id }); const commentId = insertComment({ dumpId, userId: author.id, body: "orig" }); assertEquals( (await patch(`/api/comments/${commentId}`, { auth: other.auth, json: { body: "x" }, })) .status, 401, ); const edited = await patch(`/api/comments/${commentId}`, { auth: author.auth, json: { body: " edited " }, }); assertEquals(edited.status, 200); assertEquals(edited.data.body, "edited"); assertExists(edited.data.updatedAt); assertEquals( (await patch(`/api/comments/${commentId}`, { auth: mod.auth, json: { body: "m" }, })) .status, 200, ); assertEquals( (await patch(`/api/comments/${commentId}`, { auth: author.auth, json: { body: "" }, })) .status, 400, ); assertEquals( (await patch(`/api/comments/${crypto.randomUUID()}`, { auth: author.auth, json: { body: "x" }, })).status, 404, ); }); test("DELETE /api/comments/:id soft-deletes, and a deleted comment can't be edited", async () => { const author = await createUser(); const other = await createUser(); const { id: dumpId } = insertDump({ userId: author.id }); const commentId = insertComment({ dumpId, userId: author.id, body: "gone soon", }); assertEquals( (await del(`/api/comments/${commentId}`, { auth: other.auth })).status, 401, ); assertEquals( (await del(`/api/comments/${commentId}`, { auth: author.auth })).status, 200, ); const [c] = (await get(`/api/dumps/${dumpId}/comments`)).data; assertEquals(c.deleted, true); assertEquals(c.body, ""); assertEquals((await get(`/api/dumps/${dumpId}`)).data.commentCount, 0); assertEquals( (await patch(`/api/comments/${commentId}`, { auth: author.auth, json: { body: "back" }, })) .status, 400, ); }); test("comment likes: count, conflicts, likers list and notifications", async () => { const author = await createUser(); const liker = await createUser(); const { id: dumpId } = insertDump({ userId: author.id }); const commentId = insertComment({ dumpId, userId: author.id }); assertEquals(castCommentLike(commentId, liker.id).likeCount, 1); assertEquals(castCommentLike(commentId, author.id).likeCount, 2); try { castCommentLike(commentId, liker.id); throw new Error("expected a conflict"); } catch (err) { assertEquals((err as APIException).status, 409); } // Only the other user's like notifies the author. assertEquals( count( `SELECT COUNT(*) as n FROM notifications WHERE type = 'comment_liked';`, ), 1, ); const likers = await get>( `/api/comments/${commentId}/likers`, ); assertEquals(likers.data.items.length, 2); assertFalse("email" in likers.data.items[0]); assertEquals(removeCommentLike(commentId, liker.id).likeCount, 1); try { removeCommentLike(commentId, liker.id); throw new Error("expected a 404"); } catch (err) { assertEquals((err as APIException).status, 404); } assertEquals( (db.prepare(`SELECT like_count FROM comments WHERE id = ?;`).get( commentId, ) as { like_count: number; }).like_count, 1, ); }); test("comment likes on a private dump: owner only", async () => { const owner = await createUser(); const other = await createUser(); const { id: dumpId } = insertDump({ userId: owner.id, isPrivate: true }); const commentId = insertComment({ dumpId, userId: owner.id }); try { castCommentLike(commentId, other.id); throw new Error("expected a 404"); } catch (err) { assertEquals((err as APIException).status, 404); } assertEquals(castCommentLike(commentId, owner.id).isPrivate, true); assertEquals((await get(`/api/comments/${commentId}/likers`)).status, 404); assertEquals( (await get(`/api/comments/${commentId}/likers`, { auth: owner.auth })) .status, 200, ); });