From 1d55570abd40b73d13c70906d98e80aab0c1fca6 Mon Sep 17 00:00:00 2001 From: Jesse Wierzbinski Date: Mon, 3 Jun 2024 21:12:55 -1000 Subject: [PATCH] fix(api): :bug: Fix bugs where favourite/unfavourite could return negative values (+ add tests) --- .../api/api/v1/statuses/:id/favourite.test.ts | 55 +++++++++++ server/api/api/v1/statuses/:id/favourite.ts | 11 +-- .../api/v1/statuses/:id/unfavourite.test.ts | 91 +++++++++++++++++++ server/api/api/v1/statuses/:id/unfavourite.ts | 11 +-- server/api/api/v1/statuses/:id/unreblog.ts | 11 +-- 5 files changed, 161 insertions(+), 18 deletions(-) create mode 100644 server/api/api/v1/statuses/:id/favourite.test.ts create mode 100644 server/api/api/v1/statuses/:id/unfavourite.test.ts diff --git a/server/api/api/v1/statuses/:id/favourite.test.ts b/server/api/api/v1/statuses/:id/favourite.test.ts new file mode 100644 index 00000000..c916b9bb --- /dev/null +++ b/server/api/api/v1/statuses/:id/favourite.test.ts @@ -0,0 +1,55 @@ +import { afterAll, describe, expect, test } from "bun:test"; +import { config } from "config-manager"; +import { getTestStatuses, getTestUsers, sendTestRequest } from "~/tests/utils"; +import type { Status as APIStatus } from "~/types/mastodon/status"; +import { meta } from "./favourite"; + +const { users, tokens, deleteUsers } = await getTestUsers(5); +const timeline = (await getTestStatuses(2, users[0])).toReversed(); + +afterAll(async () => { + await deleteUsers(); +}); + +// /api/v1/statuses/:id/favourite +describe(meta.route, () => { + test("should return 401 if not authenticated", async () => { + const response = await sendTestRequest( + new Request( + new URL( + meta.route.replace(":id", timeline[0].id), + config.http.base_url, + ), + { + method: "POST", + }, + ), + ); + + expect(response.status).toBe(401); + }); + + test("should favourite post", async () => { + const response = await sendTestRequest( + new Request( + new URL( + meta.route.replace(":id", timeline[0].id), + config.http.base_url, + ), + { + method: "POST", + headers: { + Authorization: `Bearer ${tokens[1].accessToken}`, + }, + }, + ), + ); + + expect(response.status).toBe(200); + + const json = (await response.json()) as APIStatus; + + expect(json.favourited).toBe(true); + expect(json.favourites_count).toBe(1); + }); +}); diff --git a/server/api/api/v1/statuses/:id/favourite.ts b/server/api/api/v1/statuses/:id/favourite.ts index 733b5e89..412d2d0c 100644 --- a/server/api/api/v1/statuses/:id/favourite.ts +++ b/server/api/api/v1/statuses/:id/favourite.ts @@ -6,7 +6,6 @@ import { z } from "zod"; import { createLike } from "~/database/entities/Like"; import { db } from "~/drizzle/db"; import { Note } from "~/packages/database-interface/note"; -import type { Status as APIStatus } from "~/types/mastodon/status"; export const meta = applyConfig({ allowedMethods: ["POST"], @@ -56,10 +55,10 @@ export default (app: Hono) => await createLike(user, note); } - return jsonResponse({ - ...(await note.toAPI(user)), - favourited: true, - favourites_count: note.getStatus().likeCount + 1, - } as APIStatus); + const newNote = await Note.fromId(id, user.id); + + if (!newNote) return errorResponse("Record not found", 404); + + return jsonResponse(await newNote.toAPI(user)); }, ); diff --git a/server/api/api/v1/statuses/:id/unfavourite.test.ts b/server/api/api/v1/statuses/:id/unfavourite.test.ts new file mode 100644 index 00000000..662d6ddc --- /dev/null +++ b/server/api/api/v1/statuses/:id/unfavourite.test.ts @@ -0,0 +1,91 @@ +import { afterAll, beforeAll, describe, expect, test } from "bun:test"; +import { config } from "config-manager"; +import { getTestStatuses, getTestUsers, sendTestRequest } from "~/tests/utils"; +import type { Status as APIStatus } from "~/types/mastodon/status"; +import { meta } from "./unfavourite"; + +const { users, tokens, deleteUsers } = await getTestUsers(5); +const timeline = (await getTestStatuses(2, users[0])).toReversed(); + +afterAll(async () => { + await deleteUsers(); +}); + +// /api/v1/statuses/:id/unfavourite +describe(meta.route, () => { + test("should return 401 if not authenticated", async () => { + const response = await sendTestRequest( + new Request( + new URL( + meta.route.replace(":id", timeline[0].id), + config.http.base_url, + ), + { + method: "POST", + }, + ), + ); + + expect(response.status).toBe(401); + }); + + test("should be able to unfavourite post that is not favourited", async () => { + const response = await sendTestRequest( + new Request( + new URL( + meta.route.replace(":id", timeline[0].id), + config.http.base_url, + ), + { + method: "POST", + headers: { + Authorization: `Bearer ${tokens[1].accessToken}`, + }, + }, + ), + ); + + expect(response.status).toBe(200); + }); + + test("should unfavourite post", async () => { + beforeAll(async () => { + await sendTestRequest( + new Request( + new URL( + `/api/v1/statuses/${timeline[1].id}/favourite`, + config.http.base_url, + ), + { + method: "POST", + headers: { + Authorization: `Bearer ${tokens[1].accessToken}`, + }, + }, + ), + ); + }); + + const response = await sendTestRequest( + new Request( + new URL( + meta.route.replace(":id", timeline[1].id), + config.http.base_url, + ), + { + method: "POST", + headers: { + Authorization: `Bearer ${tokens[1].accessToken}`, + }, + }, + ), + ); + + expect(response.status).toBe(200); + + const json = (await response.json()) as APIStatus; + + expect(json.favourited).toBe(false); + expect(json.favourites_count).toBe(0); + }); +}); diff --git a/server/api/api/v1/statuses/:id/unfavourite.ts b/server/api/api/v1/statuses/:id/unfavourite.ts index 0f7b5cbd..f86519a3 100644 --- a/server/api/api/v1/statuses/:id/unfavourite.ts +++ b/server/api/api/v1/statuses/:id/unfavourite.ts @@ -5,7 +5,6 @@ import type { Hono } from "hono"; import { z } from "zod"; import { deleteLike } from "~/database/entities/Like"; import { Note } from "~/packages/database-interface/note"; -import type { Status as APIStatus } from "~/types/mastodon/status"; export const meta = applyConfig({ allowedMethods: ["POST"], @@ -44,10 +43,10 @@ export default (app: Hono) => await deleteLike(user, note); - return jsonResponse({ - ...(await note.toAPI(user)), - favourited: false, - favourites_count: note.getStatus().likeCount - 1, - } as APIStatus); + const newNote = await Note.fromId(id, user.id); + + if (!newNote) return errorResponse("Record not found", 404); + + return jsonResponse(await newNote.toAPI(user)); }, ); diff --git a/server/api/api/v1/statuses/:id/unreblog.ts b/server/api/api/v1/statuses/:id/unreblog.ts index ff86a321..b3b9feae 100644 --- a/server/api/api/v1/statuses/:id/unreblog.ts +++ b/server/api/api/v1/statuses/:id/unreblog.ts @@ -6,7 +6,6 @@ import type { Hono } from "hono"; import { z } from "zod"; import { Notes } from "~/drizzle/schema"; import { Note } from "~/packages/database-interface/note"; -import type { Status as APIStatus } from "~/types/mastodon/status"; export const meta = applyConfig({ allowedMethods: ["POST"], @@ -59,10 +58,10 @@ export default (app: Hono) => await existingReblog.delete(); - return jsonResponse({ - ...(await foundStatus.toAPI(user)), - reblogged: false, - reblogs_count: foundStatus.getStatus().reblogCount - 1, - } as APIStatus); + const newNote = await Note.fromId(id, user.id); + + if (!newNote) return errorResponse("Record not found", 404); + + return jsonResponse(await newNote.toAPI(user)); }, );