From dd55a7257ba675803629252e38e3eccb1a338e74 Mon Sep 17 00:00:00 2001
From: Nad Alaba <37968805+NadAlaba@users.noreply.github.com>
Date: Wed, 30 Jul 2025 13:50:36 +0300
Subject: [PATCH 1/2] fix: cursor not changing to pointer on button hover
(@NadAlaba) (#6801)
- bug was introduced in
[#6651](https://github.com/monkeytypegame/monkeytype/pull/6651) by
changing the inline style `cursor` to `default` when focus mode is
disabled instead of removing the `cursor: none` inline style.
- add a `.clickable` class to clickable banners/PSAs instead of relying
on `cursor: pointer` inline style which may be cleared by
`.css("cursor", "")` in the previous commit.
- make the test for clickable banners/PSAs case insensitive and allow
`` elements in the test.
---
frontend/src/styles/banners.scss | 4 ++++
frontend/src/ts/elements/notifications.ts | 4 ++--
frontend/src/ts/test/focus.ts | 6 +++---
3 files changed, 9 insertions(+), 5 deletions(-)
diff --git a/frontend/src/styles/banners.scss b/frontend/src/styles/banners.scss
index f913bf66c513..fc169cc8ea6a 100644
--- a/frontend/src/styles/banners.scss
+++ b/frontend/src/styles/banners.scss
@@ -9,6 +9,10 @@
color: var(--bg-color);
justify-content: center;
+ &.clickable {
+ cursor: pointer;
+ }
+
&.withImage {
.lefticon {
display: none;
diff --git a/frontend/src/ts/elements/notifications.ts b/frontend/src/ts/elements/notifications.ts
index d73c5c48817f..351944361152 100644
--- a/frontend/src/ts/elements/notifications.ts
+++ b/frontend/src/ts/elements/notifications.ts
@@ -193,11 +193,11 @@ class Notification {
});
}
// NOTE: This need to be changed if the update banner text is changed
- if (this.message.includes("please refresh")) {
+ if (/please ()?refresh/i.test(this.message)) {
// add pointer when refresh is needed
$(
`#bannerCenter .banner[id='${this.id}'], #bannerCenter .psa[id='${this.id}']`
- ).css("cursor", "pointer");
+ ).addClass("clickable");
// refresh on clicking banner
$(
`#bannerCenter .banner[id='${this.id}'], #bannerCenter .psa[id='${this.id}']`
diff --git a/frontend/src/ts/test/focus.ts b/frontend/src/ts/test/focus.ts
index 1062193de673..498d816bc868 100644
--- a/frontend/src/ts/test/focus.ts
+++ b/frontend/src/ts/test/focus.ts
@@ -36,9 +36,9 @@ export function set(foc: boolean, withCursor = false): void {
Caret.startAnimation();
$("header").removeClass("focus");
$("footer").removeClass("focus");
- $("body").css("cursor", "default");
- $("button").css("cursor", "default");
- $("a").css("cursor", "default");
+ $("body").css("cursor", "");
+ $("button").css("cursor", "");
+ $("a").css("cursor", "");
$("main").removeClass("focus");
$("#bannerCenter").removeClass("focus");
$("#notificationCenter").removeClass("focus");
From c1a681c17f66654c08507229fe9e811c6d72e2b0 Mon Sep 17 00:00:00 2001
From: Christian Fehmer
Date: Wed, 30 Jul 2025 13:22:40 +0200
Subject: [PATCH 2/2] test: split integration tests (@fehmer) (#6807)
- **trigger**
- **test: split integration tests (@fehmer)**
---
.../__migration__/testActivity.spec.ts | 13 ++-
.../dal/admin-uids.spec.ts | 5 +-
.../dal/ape-keys.spec.ts | 5 +-
.../dal/blocklist.spec.ts | 5 +-
.../dal/leaderboards.spec.ts | 36 ++----
.../{ => __integration__}/dal/preset.spec.ts | 5 +-
.../{ => __integration__}/dal/public.spec.ts | 5 +-
.../{ => __integration__}/dal/result.spec.ts | 9 +-
.../{ => __integration__}/dal/user.spec.ts | 7 +-
backend/__tests__/__integration__/index.ts | 5 +
.../setup-integration-tests.ts | 65 +++++++++++
backend/__tests__/__testData__/users.ts | 19 +++
.../api/controllers/leaderboard.spec.ts | 6 +-
.../__tests__/api/controllers/result.spec.ts | 49 ++++++--
.../__tests__/api/controllers/user.spec.ts | 108 +++++++++++-------
backend/__tests__/global-setup.ts | 10 +-
backend/__tests__/setup-common-mocks.ts | 40 +++++++
backend/__tests__/setup-tests.ts | 87 ++++----------
backend/package.json | 3 +-
backend/vitest.config.js | 5 +-
package.json | 2 +-
turbo.json | 4 +
22 files changed, 321 insertions(+), 172 deletions(-)
rename backend/__tests__/{ => __integration__}/__migration__/testActivity.spec.ts (82%)
rename backend/__tests__/{ => __integration__}/dal/admin-uids.spec.ts (82%)
rename backend/__tests__/{ => __integration__}/dal/ape-keys.spec.ts (76%)
rename backend/__tests__/{ => __integration__}/dal/blocklist.spec.ts (98%)
rename backend/__tests__/{ => __integration__}/dal/leaderboards.spec.ts (93%)
rename backend/__tests__/{ => __integration__}/dal/preset.spec.ts (98%)
rename backend/__tests__/{ => __integration__}/dal/public.spec.ts (87%)
rename backend/__tests__/{ => __integration__}/dal/result.spec.ts (95%)
rename backend/__tests__/{ => __integration__}/dal/user.spec.ts (99%)
create mode 100644 backend/__tests__/__integration__/index.ts
create mode 100644 backend/__tests__/__integration__/setup-integration-tests.ts
create mode 100644 backend/__tests__/setup-common-mocks.ts
diff --git a/backend/__tests__/__migration__/testActivity.spec.ts b/backend/__tests__/__integration__/__migration__/testActivity.spec.ts
similarity index 82%
rename from backend/__tests__/__migration__/testActivity.spec.ts
rename to backend/__tests__/__integration__/__migration__/testActivity.spec.ts
index 2b65382fbbdd..3337e35bb0a4 100644
--- a/backend/__tests__/__migration__/testActivity.spec.ts
+++ b/backend/__tests__/__integration__/__migration__/testActivity.spec.ts
@@ -1,10 +1,11 @@
-import * as Migration from "../../__migration__/testActivity";
-import * as UserTestData from "../__testData__/users";
-import * as UserDal from "../../src/dal/user";
-import * as ResultDal from "../../src/dal/result";
-import { DBResult } from "../../src/utils/result";
+import * as Migration from "../../../__migration__/testActivity";
+import * as UserTestData from "../../__testData__/users";
+import * as UserDal from "../../../src/dal/user";
+import * as ResultDal from "../../../src/dal/result";
+import { DBResult } from "../../../src/utils/result";
+import { describeIntegration } from "..";
-describe("testActivity migration", () => {
+describeIntegration()("testActivity migration", () => {
it("migrates users without results", async () => {
//given
const user1 = await UserTestData.createUser();
diff --git a/backend/__tests__/dal/admin-uids.spec.ts b/backend/__tests__/__integration__/dal/admin-uids.spec.ts
similarity index 82%
rename from backend/__tests__/dal/admin-uids.spec.ts
rename to backend/__tests__/__integration__/dal/admin-uids.spec.ts
index daadbbcab537..aad7271d2aca 100644
--- a/backend/__tests__/dal/admin-uids.spec.ts
+++ b/backend/__tests__/__integration__/dal/admin-uids.spec.ts
@@ -1,7 +1,8 @@
import { ObjectId } from "mongodb";
-import * as AdminUidsDal from "../../src/dal/admin-uids";
+import * as AdminUidsDal from "../../../src/dal/admin-uids";
+import { describeIntegration } from "..";
-describe("AdminUidsDal", () => {
+describeIntegration()("AdminUidsDal", () => {
describe("isAdmin", () => {
it("should return true for existing admin user", async () => {
//GIVEN
diff --git a/backend/__tests__/dal/ape-keys.spec.ts b/backend/__tests__/__integration__/dal/ape-keys.spec.ts
similarity index 76%
rename from backend/__tests__/dal/ape-keys.spec.ts
rename to backend/__tests__/__integration__/dal/ape-keys.spec.ts
index b92a3ae4acd7..a37c907280d1 100644
--- a/backend/__tests__/dal/ape-keys.spec.ts
+++ b/backend/__tests__/__integration__/dal/ape-keys.spec.ts
@@ -1,7 +1,8 @@
import { ObjectId } from "mongodb";
-import { addApeKey } from "../../src/dal/ape-keys";
+import { addApeKey } from "../../../src/dal/ape-keys";
+import { describeIntegration } from "..";
-describe("ApeKeysDal", () => {
+describeIntegration()("ApeKeysDal", () => {
it("should be able to add a new ape key", async () => {
const apeKey = {
_id: new ObjectId(),
diff --git a/backend/__tests__/dal/blocklist.spec.ts b/backend/__tests__/__integration__/dal/blocklist.spec.ts
similarity index 98%
rename from backend/__tests__/dal/blocklist.spec.ts
rename to backend/__tests__/__integration__/dal/blocklist.spec.ts
index 1678d5eb98c3..440cbfe3d6c0 100644
--- a/backend/__tests__/dal/blocklist.spec.ts
+++ b/backend/__tests__/__integration__/dal/blocklist.spec.ts
@@ -1,7 +1,8 @@
import { ObjectId } from "mongodb";
-import * as BlacklistDal from "../../src/dal/blocklist";
+import * as BlacklistDal from "../../../src/dal/blocklist";
+import { describeIntegration } from "..";
-describe("BlocklistDal", () => {
+describeIntegration()("BlocklistDal", () => {
describe("add", () => {
beforeEach(() => {
vitest.useFakeTimers();
diff --git a/backend/__tests__/dal/leaderboards.spec.ts b/backend/__tests__/__integration__/dal/leaderboards.spec.ts
similarity index 93%
rename from backend/__tests__/dal/leaderboards.spec.ts
rename to backend/__tests__/__integration__/dal/leaderboards.spec.ts
index 4181852671c3..9e7bd5e1347f 100644
--- a/backend/__tests__/dal/leaderboards.spec.ts
+++ b/backend/__tests__/__integration__/dal/leaderboards.spec.ts
@@ -1,17 +1,19 @@
import _ from "lodash";
import { ObjectId } from "mongodb";
-import * as UserDal from "../../src/dal/user";
-import * as LeaderboardsDal from "../../src/dal/leaderboards";
-import * as PublicDal from "../../src/dal/public";
-import * as Configuration from "../../src/init/configuration";
-import type { DBLeaderboardEntry } from "../../src/dal/leaderboards";
+import * as UserDal from "../../../src/dal/user";
+import * as LeaderboardsDal from "../../../src/dal/leaderboards";
+import * as PublicDal from "../../../src/dal/public";
+import * as Configuration from "../../../src/init/configuration";
+import type { DBLeaderboardEntry } from "../../../src/dal/leaderboards";
import type { PersonalBest } from "@monkeytype/schemas/shared";
const configuration = Configuration.getCachedConfiguration();
-import * as DB from "../../src/init/db";
-import { LbPersonalBests } from "../../src/utils/pb";
+import * as DB from "../../../src/init/db";
+import { LbPersonalBests } from "../../../src/utils/pb";
+import { describeIntegration } from "..";
+import { pb } from "../../__testData__/users";
-describe("LeaderboardsDal", () => {
+describeIntegration()("LeaderboardsDal", () => {
describe("update", () => {
it("should ignore unapplicable users on leaderboard", async () => {
//GIVEN
@@ -328,24 +330,6 @@ function lbBests(pb15?: PersonalBest, pb60?: PersonalBest): LbPersonalBests {
return result;
}
-export function pb(
- wpm: number,
- acc: number = 90,
- timestamp: number = 1
-): PersonalBest {
- return {
- acc,
- consistency: 100,
- difficulty: "normal",
- lazyMode: false,
- language: "english",
- punctuation: false,
- raw: wpm + 1,
- wpm,
- timestamp,
- };
-}
-
function premium(expirationDeltaSeconds: number) {
return {
premium: {
diff --git a/backend/__tests__/dal/preset.spec.ts b/backend/__tests__/__integration__/dal/preset.spec.ts
similarity index 98%
rename from backend/__tests__/dal/preset.spec.ts
rename to backend/__tests__/__integration__/dal/preset.spec.ts
index b97862a5654e..ec938216d25c 100644
--- a/backend/__tests__/dal/preset.spec.ts
+++ b/backend/__tests__/__integration__/dal/preset.spec.ts
@@ -1,8 +1,9 @@
import { ObjectId } from "mongodb";
-import * as PresetDal from "../../src/dal/preset";
+import * as PresetDal from "../../../src/dal/preset";
import _ from "lodash";
+import { describeIntegration } from "..";
-describe("PresetDal", () => {
+describeIntegration()("PresetDal", () => {
describe("readPreset", () => {
it("should read", async () => {
//GIVEN
diff --git a/backend/__tests__/dal/public.spec.ts b/backend/__tests__/__integration__/dal/public.spec.ts
similarity index 87%
rename from backend/__tests__/dal/public.spec.ts
rename to backend/__tests__/__integration__/dal/public.spec.ts
index 92b9aedf5c12..7a372c3b0fac 100644
--- a/backend/__tests__/dal/public.spec.ts
+++ b/backend/__tests__/__integration__/dal/public.spec.ts
@@ -1,6 +1,7 @@
-import * as PublicDAL from "../../src/dal/public";
+import { describeIntegration } from "..";
+import * as PublicDAL from "../../../src/dal/public";
-describe("PublicDAL", function () {
+describeIntegration()("PublicDAL", function () {
it("should be able to update stats", async function () {
// checks it doesn't throw an error. the actual values are checked in another test.
await PublicDAL.updateStats(1, 15);
diff --git a/backend/__tests__/dal/result.spec.ts b/backend/__tests__/__integration__/dal/result.spec.ts
similarity index 95%
rename from backend/__tests__/dal/result.spec.ts
rename to backend/__tests__/__integration__/dal/result.spec.ts
index 87834e83cad2..db46f7c1bb45 100644
--- a/backend/__tests__/dal/result.spec.ts
+++ b/backend/__tests__/__integration__/dal/result.spec.ts
@@ -1,7 +1,8 @@
-import * as ResultDal from "../../src/dal/result";
+import * as ResultDal from "../../../src/dal/result";
import { ObjectId } from "mongodb";
-import * as UserDal from "../../src/dal/user";
-import { DBResult } from "../../src/utils/result";
+import * as UserDal from "../../../src/dal/user";
+import { DBResult } from "../../../src/utils/result";
+import { describeIntegration } from "..";
let uid: string;
const timestamp = Date.now() - 60000;
@@ -62,7 +63,7 @@ async function createDummyData(
});
}
}
-describe("ResultDal", () => {
+describeIntegration()("ResultDal", () => {
beforeEach(() => {
uid = new ObjectId().toHexString();
});
diff --git a/backend/__tests__/dal/user.spec.ts b/backend/__tests__/__integration__/dal/user.spec.ts
similarity index 99%
rename from backend/__tests__/dal/user.spec.ts
rename to backend/__tests__/__integration__/dal/user.spec.ts
index d763a06efa6d..13eb3ecbcc80 100644
--- a/backend/__tests__/dal/user.spec.ts
+++ b/backend/__tests__/__integration__/dal/user.spec.ts
@@ -1,10 +1,11 @@
import _ from "lodash";
-import * as UserDAL from "../../src/dal/user";
-import * as UserTestData from "../__testData__/users";
+import * as UserDAL from "../../../src/dal/user";
+import * as UserTestData from "../../__testData__/users";
import { ObjectId } from "mongodb";
import { MonkeyMail, ResultFilters } from "@monkeytype/schemas/users";
import { PersonalBest, PersonalBests } from "@monkeytype/schemas/shared";
import { CustomThemeColors } from "@monkeytype/schemas/configs";
+import { describeIntegration } from "..";
const mockPersonalBest = {
acc: 1,
@@ -85,7 +86,7 @@ const mockResultFilter: ResultFilters = {
const mockDbResultFilter = { ...mockResultFilter, _id: new ObjectId() };
-describe("UserDal", () => {
+describeIntegration()("UserDal", () => {
it("should be able to insert users", async () => {
// given
const newUser = {
diff --git a/backend/__tests__/__integration__/index.ts b/backend/__tests__/__integration__/index.ts
new file mode 100644
index 000000000000..66c81ce329b1
--- /dev/null
+++ b/backend/__tests__/__integration__/index.ts
@@ -0,0 +1,5 @@
+export const isIntegrationTest = process.env["INTEGRATION_TESTS"] === "true";
+
+export function describeIntegration() {
+ return describe.runIf(isIntegrationTest);
+}
diff --git a/backend/__tests__/__integration__/setup-integration-tests.ts b/backend/__tests__/__integration__/setup-integration-tests.ts
new file mode 100644
index 000000000000..7ce1af29a352
--- /dev/null
+++ b/backend/__tests__/__integration__/setup-integration-tests.ts
@@ -0,0 +1,65 @@
+import { Collection, Db, MongoClient, WithId } from "mongodb";
+import { afterAll, beforeAll, afterEach } from "vitest";
+import * as MongoDbMock from "vitest-mongodb";
+import { MongoDbMockConfig } from "../global-setup";
+import { isIntegrationTest } from ".";
+import { setupCommonMocks } from "../setup-common-mocks";
+
+process.env["MODE"] = "dev";
+//process.env["MONGOMS_DISTRO"] = "ubuntu-22.04";
+
+if (!isIntegrationTest) {
+ console.error("wrong environment");
+ process.exit();
+}
+
+if (!process.env["REDIS_URI"]) {
+ // use mock if not set
+ process.env["REDIS_URI"] = "redis://mock";
+}
+
+let db: Db;
+let client: MongoClient;
+const collectionsForCleanUp = ["users"];
+
+beforeAll(async () => {
+ //don't add any configuration here, add to global-setup.ts instead.
+
+ console.log("integration setup mongo");
+ await MongoDbMock.setup(MongoDbMockConfig);
+
+ client = new MongoClient(globalThis.__MONGO_URI__);
+ await client.connect();
+ db = client.db();
+
+ vi.mock("../../src/init/db", () => ({
+ __esModule: true,
+ getDb: (): Db => db,
+ collection: (name: string): Collection> =>
+ db.collection>(name),
+ close: () => {
+ //
+ },
+ }));
+ setupCommonMocks();
+});
+
+afterEach(async () => {
+ if (globalThis.__MONGO_URI__) {
+ await Promise.all(
+ collectionsForCleanUp.map((collection) =>
+ db.collection(collection).deleteMany({})
+ )
+ );
+ }
+});
+
+afterAll(async () => {
+ await client?.close();
+ await MongoDbMock.teardown();
+ // @ts-ignore
+ db = undefined;
+ //@ts-ignore
+ client = undefined;
+ vi.resetAllMocks();
+});
diff --git a/backend/__tests__/__testData__/users.ts b/backend/__tests__/__testData__/users.ts
index 26c27b5b660d..9b6faa31c4f3 100644
--- a/backend/__tests__/__testData__/users.ts
+++ b/backend/__tests__/__testData__/users.ts
@@ -1,6 +1,7 @@
import * as DB from "../../src/init/db";
import * as UserDAL from "../../src/dal/user";
import { ObjectId } from "mongodb";
+import { PersonalBest } from "@monkeytype/schemas/shared";
export async function createUser(
user?: Partial
@@ -24,3 +25,21 @@ export async function createUserWithoutMigration(
return await UserDAL.getUser(uid, "test");
}
+
+export function pb(
+ wpm: number,
+ acc: number = 90,
+ timestamp: number = 1
+): PersonalBest {
+ return {
+ acc,
+ consistency: 100,
+ difficulty: "normal",
+ lazyMode: false,
+ language: "english",
+ punctuation: false,
+ raw: wpm + 1,
+ wpm,
+ timestamp,
+ };
+}
diff --git a/backend/__tests__/api/controllers/leaderboard.spec.ts b/backend/__tests__/api/controllers/leaderboard.spec.ts
index a7de03ada532..7a436bab64dc 100644
--- a/backend/__tests__/api/controllers/leaderboard.spec.ts
+++ b/backend/__tests__/api/controllers/leaderboard.spec.ts
@@ -36,16 +36,18 @@ describe("Loaderboard Controller", () => {
});
describe("get leaderboard", () => {
const getLeaderboardMock = vi.spyOn(LeaderboardDal, "get");
+ const getLeaderboardCountMock = vi.spyOn(LeaderboardDal, "getCount");
beforeEach(() => {
getLeaderboardMock.mockReset();
+ getLeaderboardCountMock.mockReset();
});
it("should get for english time 60", async () => {
//GIVEN
const resultData = {
- count: 0,
+ count: 42,
pageSize: 50,
entries: [
{
@@ -78,6 +80,7 @@ describe("Loaderboard Controller", () => {
_id: new ObjectId(),
}));
getLeaderboardMock.mockResolvedValue(mockData);
+ getLeaderboardCountMock.mockResolvedValue(42);
//WHEN
@@ -104,6 +107,7 @@ describe("Loaderboard Controller", () => {
it("should get for english time 60 with page", async () => {
//GIVEN
getLeaderboardMock.mockResolvedValue([]);
+ getLeaderboardCountMock.mockResolvedValue(0);
const page = 0;
const pageSize = 25;
diff --git a/backend/__tests__/api/controllers/result.spec.ts b/backend/__tests__/api/controllers/result.spec.ts
index 8d90dca5e512..c24a2b8c8115 100644
--- a/backend/__tests__/api/controllers/result.spec.ts
+++ b/backend/__tests__/api/controllers/result.spec.ts
@@ -5,6 +5,7 @@ import * as Configuration from "../../../src/init/configuration";
import * as ResultDal from "../../../src/dal/result";
import * as UserDal from "../../../src/dal/user";
import * as LogsDal from "../../../src/dal/logs";
+import * as PublicDal from "../../../src/dal/public";
import { ObjectId } from "mongodb";
import {
mockAuthenticateWithApeKey,
@@ -504,6 +505,7 @@ describe("result controller test", () => {
it("should delete", async () => {
//GIVEN
mockAuth.modifyToken({ iat: Date.now() - 1000 });
+ deleteAllMock.mockResolvedValue(undefined as any);
//WHEN
const { body } = await mockApp
@@ -672,22 +674,34 @@ describe("result controller test", () => {
describe("addResult", () => {
//TODO improve test coverage for addResult
const insertedId = new ObjectId();
- const getUserMock = vi.spyOn(UserDal, "getUser");
- const updateStreakMock = vi.spyOn(UserDal, "updateStreak");
- const checkIfTagPbMock = vi.spyOn(UserDal, "checkIfTagPb");
- const addResultMock = vi.spyOn(ResultDal, "addResult");
+ const userGetMock = vi.spyOn(UserDal, "getUser");
+ const userUpdateStreakMock = vi.spyOn(UserDal, "updateStreak");
+ const userCheckIfTagPbMock = vi.spyOn(UserDal, "checkIfTagPb");
+ const userCheckIfPbMock = vi.spyOn(UserDal, "checkIfPb");
+ const userIncrementXpMock = vi.spyOn(UserDal, "incrementXp");
+ const userUpdateTypingStatsMock = vi.spyOn(UserDal, "updateTypingStats");
+ const resultAddMock = vi.spyOn(ResultDal, "addResult");
+ const publicUpdateStatsMock = vi.spyOn(PublicDal, "updateStats");
beforeEach(async () => {
await enableResultsSaving(true);
- [getUserMock, updateStreakMock, checkIfTagPbMock, addResultMock].forEach(
- (it) => it.mockReset()
- );
+ [
+ userGetMock,
+ userUpdateStreakMock,
+ userCheckIfTagPbMock,
+ userCheckIfPbMock,
+ userIncrementXpMock,
+ userUpdateTypingStatsMock,
+ resultAddMock,
+ publicUpdateStatsMock,
+ ].forEach((it) => it.mockReset());
- getUserMock.mockResolvedValue({ name: "bob" } as any);
- updateStreakMock.mockResolvedValue(0);
- checkIfTagPbMock.mockResolvedValue([]);
- addResultMock.mockResolvedValue({ insertedId });
+ userGetMock.mockResolvedValue({ name: "bob" } as any);
+ userUpdateStreakMock.mockResolvedValue(0);
+ userCheckIfTagPbMock.mockResolvedValue([]);
+ userCheckIfPbMock.mockResolvedValue(true);
+ resultAddMock.mockResolvedValue({ insertedId });
});
it("should add result", async () => {
@@ -749,7 +763,7 @@ describe("result controller test", () => {
insertedId: insertedId.toHexString(),
});
- expect(addResultMock).toHaveBeenCalledWith(
+ expect(resultAddMock).toHaveBeenCalledWith(
uid,
expect.objectContaining({
acc: 86,
@@ -783,6 +797,17 @@ describe("result controller test", () => {
wpm: 80,
})
);
+
+ expect(publicUpdateStatsMock).toHaveBeenCalledWith(
+ 4,
+ 15.1 + 2 - 5 //duration + incompleteTestSeconds-afk
+ );
+ expect(userIncrementXpMock).toHaveBeenCalledWith(uid, 0);
+ expect(userUpdateTypingStatsMock).toHaveBeenCalledWith(
+ uid,
+ 4,
+ 15.1 + 2 - 5 //duration + incompleteTestSeconds-afk
+ );
});
it("should fail if result saving is disabled", async () => {
//GIVEN
diff --git a/backend/__tests__/api/controllers/user.spec.ts b/backend/__tests__/api/controllers/user.spec.ts
index f32d40dbe733..27dfe0722c75 100644
--- a/backend/__tests__/api/controllers/user.spec.ts
+++ b/backend/__tests__/api/controllers/user.spec.ts
@@ -20,7 +20,6 @@ import * as ApeKeysDal from "../../../src/dal/ape-keys";
import * as LogDal from "../../../src/dal/logs";
import { ObjectId } from "mongodb";
import { PersonalBest } from "@monkeytype/schemas/shared";
-import { pb } from "../../dal/leaderboards.spec";
import {
mockAuthenticateWithApeKey,
mockBearerAuthentication,
@@ -31,6 +30,7 @@ import { MonkeyMail, UserStreak } from "@monkeytype/schemas/users";
import MonkeyError, { isFirebaseError } from "../../../src/utils/error";
import { LeaderboardEntry } from "@monkeytype/schemas/leaderboards";
import * as WeeklyXpLeaderboard from "../../../src/services/weekly-xp-leaderboard";
+import { pb } from "../../__testData__/users";
const mockApp = request(app);
const configuration = Configuration.getCachedConfiguration();
@@ -41,43 +41,7 @@ describe("user controller test", () => {
beforeEach(() => {
mockAuth.beforeEach();
});
- describe("user creation flow", () => {
- beforeEach(async () => {
- await enableSignup(true);
- });
- it("should be able to check name, sign up, and get user data", async () => {
- await mockApp.get("/users/checkName/NewUser").expect(200);
- const newUser = {
- name: "NewUser",
- uid,
- email: "newuser@mail.com",
- captcha: "captcha",
- };
-
- await mockApp
- .post("/users/signup")
- .set("Authorization", `Bearer ${uid}`)
- .send(newUser)
- .expect(200);
-
- const response = await mockApp
- .get("/users")
- .set("Authorization", `Bearer ${uid}`)
- .send()
- .expect(200);
-
- const {
- body: { data: userData },
- } = response;
-
- expect(userData.name).toBe(newUser.name);
- expect(userData.email).toBe(newUser.email);
- expect(userData.uid).toBe(newUser.uid);
-
- await mockApp.get("/users/checkName/NewUser").expect(409);
- });
- });
describe("user signup", () => {
const blocklistContainsMock = vi.spyOn(BlocklistDal, "contains");
const firebaseDeleteUserMock = vi.spyOn(AuthUtils, "deleteUser");
@@ -253,6 +217,64 @@ describe("user controller test", () => {
});
});
});
+ describe("checkName", () => {
+ const userIsNameAvailableMock = vi.spyOn(UserDal, "isNameAvailable");
+
+ beforeEach(() => {
+ userIsNameAvailableMock.mockReset();
+ });
+
+ it("returns ok if name is available", async () => {
+ //GIVEN
+ userIsNameAvailableMock.mockResolvedValue(true);
+
+ //WHEN
+ const { body } = await mockApp
+ .get("/users/checkName/bob")
+ //no authentication required
+ .expect(200);
+
+ //THEN
+ expect(body).toEqual({
+ message: "Username available",
+ data: null,
+ });
+ expect(userIsNameAvailableMock).toHaveBeenCalledWith("bob", "");
+ });
+
+ it("returns 409 if name is not available", async () => {
+ //GIVEN
+ userIsNameAvailableMock.mockResolvedValue(false);
+
+ //WHEN
+ const { body } = await mockApp
+ .get("/users/checkName/bob")
+ //no authentication required
+ .expect(409);
+
+ //THEN
+ expect(body.message).toEqual("Username unavailable");
+
+ expect(userIsNameAvailableMock).toHaveBeenCalledWith("bob", "");
+ });
+ it("returns ok if name is our own", async () => {
+ //GIVEN
+ userIsNameAvailableMock.mockResolvedValue(true);
+
+ //WHEN
+ const { body } = await mockApp
+ .get("/users/checkName/bob")
+ .set("Authorization", `Bearer ${uid}`)
+ .expect(200);
+
+ //THEN
+ expect(body).toEqual({
+ message: "Username available",
+ data: null,
+ });
+ expect(userIsNameAvailableMock).toHaveBeenCalledWith("bob", uid);
+ });
+ });
describe("sendVerificationEmail", () => {
const adminGetUserMock = vi.fn();
const adminGenerateVerificationLinkMock = vi.fn();
@@ -602,6 +624,7 @@ describe("user controller test", () => {
"purgeUserFromXpLeaderboards"
);
const blocklistAddMock = vi.spyOn(BlocklistDal, "add");
+ const logsDeleteUserMock = vi.spyOn(LogDal, "deleteUserLogs");
beforeEach(() => {
mockAuth.beforeEach();
@@ -631,6 +654,7 @@ describe("user controller test", () => {
deleteAllPresetsMock,
purgeUserFromDailyLeaderboardsMock,
purgeUserFromXpLeaderboardsMock,
+ logsDeleteUserMock,
].forEach((it) => it.mockReset());
});
@@ -668,6 +692,7 @@ describe("user controller test", () => {
uid,
(await configuration).leaderboards.weeklyXp
);
+ expect(logsDeleteUserMock).toHaveBeenCalledWith(uid);
});
it("should delete user without adding to blocklist if not banned", async () => {
//GIVEN
@@ -702,6 +727,7 @@ describe("user controller test", () => {
uid,
(await configuration).leaderboards.weeklyXp
);
+ expect(logsDeleteUserMock).toHaveBeenCalledWith(uid);
});
it("should not fail if userInfo cannot be found", async () => {
@@ -731,6 +757,7 @@ describe("user controller test", () => {
uid,
(await configuration).leaderboards.weeklyXp
);
+ expect(logsDeleteUserMock).toHaveBeenCalledWith(uid);
});
it("should fail for unknown error from UserDal", async () => {
@@ -759,6 +786,7 @@ describe("user controller test", () => {
uid,
(await configuration).leaderboards.weeklyXp
);
+ expect(logsDeleteUserMock).not.toHaveBeenCalled();
});
it("should not fail if firebase user cannot be found", async () => {
//GIVEN
@@ -798,6 +826,7 @@ describe("user controller test", () => {
uid,
(await configuration).leaderboards.weeklyXp
);
+ expect(logsDeleteUserMock).toHaveBeenCalledWith(uid);
});
it("should fail for unknown error from firebase", async () => {
@@ -3392,9 +3421,8 @@ describe("user controller test", () => {
//WHEN
const { body } = await mockApp
.patch("/users/inbox")
- .set("Authorization", `Bearer ${uid}`);
- //.expect(200);
- console.log(body);
+ .set("Authorization", `Bearer ${uid}`)
+ .expect(200);
//THEN
expect(body).toEqual({
diff --git a/backend/__tests__/global-setup.ts b/backend/__tests__/global-setup.ts
index 64c96042aa24..d039d97b1860 100644
--- a/backend/__tests__/global-setup.ts
+++ b/backend/__tests__/global-setup.ts
@@ -1,11 +1,17 @@
import * as MongoDbMock from "vitest-mongodb";
+import { isIntegrationTest } from "./__integration__";
export async function setup(): Promise {
process.env.TZ = "UTC";
- await MongoDbMock.setup(MongoDbMockConfig);
+ if (isIntegrationTest) {
+ console.log("integration download mongomock");
+ await MongoDbMock.setup(MongoDbMockConfig);
+ }
}
export async function teardown(): Promise {
- await MongoDbMock.teardown();
+ if (isIntegrationTest) {
+ await MongoDbMock.teardown();
+ }
}
export const MongoDbMockConfig = {
diff --git a/backend/__tests__/setup-common-mocks.ts b/backend/__tests__/setup-common-mocks.ts
new file mode 100644
index 000000000000..0841e1064951
--- /dev/null
+++ b/backend/__tests__/setup-common-mocks.ts
@@ -0,0 +1,40 @@
+export function setupCommonMocks() {
+ vi.mock("../src/utils/logger", () => ({
+ __esModule: true,
+ default: {
+ error: console.error,
+ warning: console.warn,
+ info: console.info,
+ success: console.info,
+ logToDb: console.info,
+ },
+ }));
+
+ vi.mock("swagger-stats", () => ({
+ getMiddleware:
+ () =>
+ (_: unknown, __: unknown, next: () => unknown): void => {
+ next();
+ },
+ }));
+
+ // TODO: better approach for this when needed
+ // https://firebase.google.com/docs/rules/unit-tests#run_local_unit_tests_with_the_version_9_javascript_sdk
+ vi.mock("firebase-admin", () => ({
+ __esModule: true,
+ default: {
+ auth: (): unknown => ({
+ verifyIdToken: (
+ _token: string,
+ _checkRevoked: boolean
+ ): unknown /* Promise */ =>
+ Promise.resolve({
+ aud: "mockFirebaseProjectId",
+ auth_time: 123,
+ exp: 1000,
+ uid: "mockUid",
+ }),
+ }),
+ },
+ }));
+}
diff --git a/backend/__tests__/setup-tests.ts b/backend/__tests__/setup-tests.ts
index 68a6d7efc5ce..3ecb35322014 100644
--- a/backend/__tests__/setup-tests.ts
+++ b/backend/__tests__/setup-tests.ts
@@ -1,94 +1,51 @@
-import { Collection, Db, MongoClient, WithId } from "mongodb";
import { afterAll, beforeAll, afterEach } from "vitest";
-import * as MongoDbMock from "vitest-mongodb";
-import { MongoDbMockConfig } from "./global-setup";
+import { isIntegrationTest } from "./__integration__";
+import { BASE_CONFIGURATION } from "../src/constants/base-configuration";
+import { setupCommonMocks } from "./setup-common-mocks";
process.env["MODE"] = "dev";
//process.env["MONGOMS_DISTRO"] = "ubuntu-22.04";
+if (isIntegrationTest) {
+ console.error("wrong environment");
+ process.exit();
+}
+
if (!process.env["REDIS_URI"]) {
// use mock if not set
process.env["REDIS_URI"] = "redis://mock";
}
-let db: Db;
-let client: MongoClient;
-const collectionsForCleanUp = ["users"];
-
beforeAll(async () => {
//don't add any configuration here, add to global-setup.ts instead.
- await MongoDbMock.setup(MongoDbMockConfig);
- client = new MongoClient(globalThis.__MONGO_URI__);
- await client.connect();
- db = client.db();
+ vi.mock("../src/dal/logs", () => ({
+ addLog: vi.fn(),
+ addImportantLog: vi.fn(),
+ deleteUserLogs: vi.fn(),
+ }));
+ vi.mock("../src/init/configuration", () => ({
+ getLiveConfiguration: () => BASE_CONFIGURATION,
+ getCachedConfiguration: () => BASE_CONFIGURATION,
+ patchConfiguration: vi.fn(),
+ }));
vi.mock("../src/init/db", () => ({
__esModule: true,
- getDb: (): Db => db,
- collection: (name: string): Collection> =>
- db.collection>(name),
+ getDb: () => undefined,
+ collection: () => undefined,
close: () => {
//
},
}));
- vi.mock("../src/utils/logger", () => ({
- __esModule: true,
- default: {
- error: console.error,
- warning: console.warn,
- info: console.info,
- success: console.info,
- logToDb: console.info,
- },
- }));
-
- vi.mock("swagger-stats", () => ({
- getMiddleware:
- () =>
- (_: unknown, __: unknown, next: () => unknown): void => {
- next();
- },
- }));
-
- // TODO: better approach for this when needed
- // https://firebase.google.com/docs/rules/unit-tests#run_local_unit_tests_with_the_version_9_javascript_sdk
- vi.mock("firebase-admin", () => ({
- __esModule: true,
- default: {
- auth: (): unknown => ({
- verifyIdToken: (
- _token: string,
- _checkRevoked: boolean
- ): unknown /* Promise */ =>
- Promise.resolve({
- aud: "mockFirebaseProjectId",
- auth_time: 123,
- exp: 1000,
- uid: "mockUid",
- }),
- }),
- },
- }));
+ setupCommonMocks();
});
afterEach(async () => {
- if (globalThis.__MONGO_URI__) {
- await Promise.all(
- collectionsForCleanUp.map((collection) =>
- db.collection(collection).deleteMany({})
- )
- );
- }
+ //noting
});
afterAll(async () => {
- await client?.close();
- await MongoDbMock.teardown();
- // @ts-ignore
- db = undefined;
- //@ts-ignore
- client = undefined;
vi.resetAllMocks();
});
diff --git a/backend/package.json b/backend/package.json
index 6e35f2544b7a..6e8726ddc280 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -12,7 +12,8 @@
"clean": "tsc --build --clean",
"ts-check": "tsc --noEmit",
"start": "node ./dist/server.js",
- "test": "vitest run",
+ "test": "vitest run --exclude '__tests__/__integration__'",
+ "integration-test": "INTEGRATION_TESTS=true vitest run __integration__",
"test-coverage": "vitest run --coverage",
"dev": "concurrently -p none \"tsx watch --clear-screen=false --inspect ./src/server.ts\" \"tsc --preserveWatchOutput --noEmit --watch\" \"esw src/ -w --ext .ts --cache --color\"",
"knip": "knip",
diff --git a/backend/vitest.config.js b/backend/vitest.config.js
index ecea6a5bbbb1..cbaa46b578d2 100644
--- a/backend/vitest.config.js
+++ b/backend/vitest.config.js
@@ -1,11 +1,14 @@
import { defineConfig } from "vitest/config";
+const isIntegrationTest = process.env["INTEGRATION_TESTS"] === "true";
export default defineConfig({
test: {
globals: true,
environment: "node",
globalSetup: "__tests__/global-setup.ts",
- setupFiles: ["__tests__/setup-tests.ts"],
+ setupFiles: isIntegrationTest
+ ? ["__tests__/__integration__/setup-integration-tests.ts"]
+ : ["__tests__/setup-tests.ts"],
pool: "forks", //this should be the default value, however the CI fails without this set.
coverage: {
diff --git a/package.json b/package.json
index 3e7aaf01af51..bf28b2edaafd 100644
--- a/package.json
+++ b/package.json
@@ -22,7 +22,7 @@
"build-fe": "turbo run build --filter @monkeytype/frontend",
"build-pkg": "turbo run build --filter=\"./packages/*\"",
"test": "turbo run test",
- "test-be": "turbo run test --filter @monkeytype/backend",
+ "test-be": "turbo run test --filter @monkeytype/backend && turbo run integration-test --filter @monkeytype/backend",
"test-fe": "turbo run test --filter @monkeytype/frontend",
"test-pkg": "turbo run test --filter=\"./packages/*\"",
"dev": "turbo run dev --force",
diff --git a/turbo.json b/turbo.json
index 8e4b84c06f51..18bb2173b0aa 100644
--- a/turbo.json
+++ b/turbo.json
@@ -19,6 +19,10 @@
"dependsOn": ["^build"],
"cache": false
},
+ "integration-test": {
+ "dependsOn": ["^build"],
+ "cache": false
+ },
"dev": {
"dependsOn": ["^build"],
"persistent": true,