All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 49s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
269 lines
7.5 KiB
TypeScript
269 lines
7.5 KiB
TypeScript
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,
|
|
type Comment,
|
|
type Dump,
|
|
type PaginatedData,
|
|
type PublicUser,
|
|
} from "../model/interfaces.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<Comment>(`/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<Comment>(`/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<Comment[]>(`/api/dumps/${id}/comments`);
|
|
assertEquals(list.data.map((c) => c.body), [
|
|
"first",
|
|
"reply",
|
|
]);
|
|
assertEquals((await get<Dump>(`/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<Comment>(`/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<Comment>(`/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<Comment[]>(`/api/dumps/${dumpId}/comments`)).data;
|
|
assertEquals(c.deleted, true);
|
|
assertEquals(c.body, "");
|
|
assertEquals((await get<Dump>(`/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<PaginatedData<PublicUser>>(
|
|
`/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,
|
|
);
|
|
});
|