diff --git a/server/.github/workflows/tests.yml b/server/.github/workflows/tests.yml index b3720732f..dd2d06f13 100644 --- a/server/.github/workflows/tests.yml +++ b/server/.github/workflows/tests.yml @@ -41,8 +41,8 @@ jobs: - name: Lint Code run: pnpm lint - - name: Run Tests In Parallel - run: pnpm paralleltest + - name: Run Tests + run: pnpm fulltest - name: Upload Coverage if: always() diff --git a/server/package.json b/server/package.json index c75d99358..e2f81fb2a 100644 --- a/server/package.json +++ b/server/package.json @@ -5,7 +5,6 @@ "main": "js/index.js", "scripts": { "fulltest": "tap", - "paralleltest": "export TACHI_PARALLEL_TESTS=true && tap -J", "watchtest": "tap --watch", "build": "tsc --project tsconfig.build.json", "lint": "eslint ./src --ext .ts --fix", diff --git a/server/src/external/mongo/db.ts b/server/src/external/mongo/db.ts index 1c29db6ab..106782651 100644 --- a/server/src/external/mongo/db.ts +++ b/server/src/external/mongo/db.ts @@ -28,7 +28,6 @@ import monk, { TMiddleware } from "monk"; import { MONGO_CONNECTION_URL, MONGO_DATABASE_NAME } from "../../lib/setup/config"; import CreateLogCtx from "../../lib/logger/logger"; import { OrphanScoreDocument } from "../../lib/score-import/import-types/common/types"; -import * as util from "util"; const logger = CreateLogCtx(__filename); @@ -36,11 +35,7 @@ let dbName = MONGO_DATABASE_NAME; /* istanbul ignore next */ if (process.env.NODE_ENV === "test") { - if (process.env.TACHI_PARALLEL_TESTS) { - dbName = `test-ephemeral-${process.pid}`; - } else { - dbName = `testingdb`; - } + dbName = `testingdb`; } let dbtime: [number, number] = [0, 0]; diff --git a/server/src/server/middleware/multer-upload.ts b/server/src/server/middleware/multer-upload.ts index 13fb4adab..f60778e10 100644 --- a/server/src/server/middleware/multer-upload.ts +++ b/server/src/server/middleware/multer-upload.ts @@ -36,6 +36,13 @@ export const CreateMulterSingleUploadMiddleware = ( }); } + if (!req.file) { + return res.status(400).json({ + success: false, + description: `Expected a file for field ${fieldName}.`, + }); + } + return next(); }); }; diff --git a/server/src/server/router/api/v1/users/_userID/banner/router.ts b/server/src/server/router/api/v1/users/_userID/banner/router.ts index 22ce16178..218aa30e5 100644 --- a/server/src/server/router/api/v1/users/_userID/banner/router.ts +++ b/server/src/server/router/api/v1/users/_userID/banner/router.ts @@ -80,7 +80,7 @@ router.get("/", async (req, res) => { if (!user.customBanner) { res.setHeader("Content-Type", "image/png"); - const buf = await CDNRetrieve("/users/defaults/banner.png"); + const buf = await CDNRetrieve("/users/default/banner.png"); return res.send(buf); } diff --git a/server/src/server/router/api/v1/users/_userID/pfp/router.test.ts b/server/src/server/router/api/v1/users/_userID/pfp/router.test.ts new file mode 100644 index 000000000..15b77fb3f --- /dev/null +++ b/server/src/server/router/api/v1/users/_userID/pfp/router.test.ts @@ -0,0 +1,78 @@ +import t from "tap"; +import db from "../../../../../../../external/mongo/db"; +import { CDNStoreOrOverwrite } from "../../../../../../../lib/cdn/cdn"; +import { GetProfilePictureURL } from "../../../../../../../lib/cdn/url-format"; +import { CloseAllConnections } from "../../../../../../../test-utils/close-connections"; +import mockApi from "../../../../../../../test-utils/mock-api"; +import ResetDBState from "../../../../../../../test-utils/resets"; +import fs from "fs"; +import { GetKTDataBuffer } from "../../../../../../../test-utils/test-data"; + +t.test("GET /api/v1/users/:userID/pfp", (t) => { + t.beforeEach(ResetDBState); + + t.test("Should return the default profile picture if user has no custom pfp", async (t) => { + await CDNStoreOrOverwrite("/users/default/pfp.png", "test"); + const res = await mockApi.get("/api/v1/users/1/pfp"); + + t.equal(res.body.toString(), "test"); + + t.end(); + }); + + t.test("Should return a custom profile picture if one is set", async (t) => { + await CDNStoreOrOverwrite(GetProfilePictureURL(1), "foo"); + await db.users.update({ id: 1 }, { $set: { customPfp: true } }); + const res = await mockApi.get("/api/v1/users/1/pfp"); + + t.equal(res.body.toString(), "foo"); + + t.end(); + }); + + t.end(); +}); + +t.test("PUT /api/v1/users/:userID/pfp", (t) => { + t.beforeEach(ResetDBState); + + t.test("Should set a profile picture if user has no custom pfp", async (t) => { + const img = GetKTDataBuffer("/images/acorn.png"); + + const res = await mockApi + .put("/api/v1/users/1/pfp") + .set("Authorization", "Bearer fake_api_token") + .attach("pfp", img, "file.jpg"); + + t.equal(res.statusCode, 200); + + const get = await mockApi.get(res.body.body.get); + + t.strictSame(img, get.body, "Profile picture should be stored."); + + t.end(); + }); + + t.test("Should set a profile picture if user has custom pfp", async (t) => { + await db.users.update({ id: 1 }, { $set: { customPfp: true } }); + + const img = GetKTDataBuffer("/images/acorn.png"); + + const res = await mockApi + .put("/api/v1/users/1/pfp") + .set("Authorization", "Bearer fake_api_token") + .attach("pfp", img, "file.jpg"); + + t.equal(res.statusCode, 200); + + const get = await mockApi.get(res.body.body.get); + + t.strictSame(img, get.body, "Profile picture should be stored."); + + t.end(); + }); + + t.end(); +}); + +t.teardown(CloseAllConnections); diff --git a/server/src/server/router/api/v1/users/_userID/pfp/router.ts b/server/src/server/router/api/v1/users/_userID/pfp/router.ts index a8079987e..2c299a161 100644 --- a/server/src/server/router/api/v1/users/_userID/pfp/router.ts +++ b/server/src/server/router/api/v1/users/_userID/pfp/router.ts @@ -29,9 +29,11 @@ router.put( async (req, res) => { const user = req[SYMBOL_TachiData]!.requestedUser!; + let updatePfp = false; + if (!user.customPfp) { logger.verbose(`User ${FormatUserDoc(user)} set a custom profile picture.`); - await db.users.update({ id: user.id }, { $set: { customPfp: true } }); + updatePfp = true; } else { logger.verbose(`User ${FormatUserDoc(user)} updated their profile picture.`); } @@ -57,6 +59,10 @@ router.put( }); } + if (updatePfp) { + await db.users.update({ id: user.id }, { $set: { customPfp: true } }); + } + return res.status(200).json({ success: true, description: `Stored profile picture.`, @@ -78,7 +84,7 @@ router.get("/", async (req, res) => { if (!user.customPfp) { res.setHeader("Content-Type", "image/png"); - const buf = await CDNRetrieve("/users/defaults/pfp.png"); + const buf = await CDNRetrieve("/users/default/pfp.png"); return res.send(buf); } diff --git a/server/src/test-utils/mock-db/api-tokens.json b/server/src/test-utils/mock-db/api-tokens.json index 769b6da56..9a7cfb7b9 100644 --- a/server/src/test-utils/mock-db/api-tokens.json +++ b/server/src/test-utils/mock-db/api-tokens.json @@ -3,7 +3,8 @@ "userID": 1, "token": "fake_api_token", "permissions": { - "submit_score": true + "submit_score": true, + "customise_profile": true }, "identifier": "Mock Token For Testing" } diff --git a/server/src/test-utils/mock-db/users.json b/server/src/test-utils/mock-db/users.json index 68a3ac27b..f1de2ec4a 100644 --- a/server/src/test-utils/mock-db/users.json +++ b/server/src/test-utils/mock-db/users.json @@ -8,8 +8,8 @@ "socialMedia": {}, "lastSeen": null, "about": "test_user_not_real", - "customPfp": true, - "customBanner": true, + "customPfp": false, + "customBanner": false, "clan": null } ] \ No newline at end of file diff --git a/server/src/test-utils/resets.ts b/server/src/test-utils/resets.ts index d96e14dda..574961f53 100644 --- a/server/src/test-utils/resets.ts +++ b/server/src/test-utils/resets.ts @@ -87,9 +87,7 @@ export function ResetCDN() { } export async function SetIndexesForDB() { - const url = `${MONGO_CONNECTION_URL}/${ - process.env.TACHI_PARALLEL_TESTS ? `test-ephemeral-${process.pid.toString()}` : "testingdb" - }`; + const url = `${MONGO_CONNECTION_URL}/testingdb`; logger.info(`Setting indexes for ${url}`); diff --git a/server/src/test-utils/test-data/images/acorn.png b/server/src/test-utils/test-data/images/acorn.png new file mode 100644 index 000000000..f2e3bdbe2 Binary files /dev/null and b/server/src/test-utils/test-data/images/acorn.png differ