test: update to vitest3 (@fehmer) (#6811)

- **test: use mongodb testcontainer (@fehmer)**
- **don't run integration tests in parallel**
- **fix premium test**
- **refactor, cleanup**
- **refactor, cleanup**
- **test: add integration tests for daily leaderboards (@fehmer)**
- **trigger**
- **trigger**
- **test: update to vitest3 (@fehmer)**
This commit is contained in:
Christian Fehmer 2025-08-04 15:55:10 +02:00 committed by GitHub
parent f759b0ce89
commit 01ed9322ec
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
60 changed files with 659 additions and 907 deletions

View file

@ -3,4 +3,4 @@ backend/__migration__
docker
backend/scripts
backend/private
**/vitest.config.js
**/vitest.config.ts

View file

@ -3,9 +3,8 @@ 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 "..";
describeIntegration()("testActivity migration", () => {
describe("testActivity migration", () => {
it("migrates users without results", async () => {
//given
const user1 = await UserTestData.createUser();

View file

@ -1,8 +1,7 @@
import { ObjectId } from "mongodb";
import * as AdminUidsDal from "../../../src/dal/admin-uids";
import { describeIntegration } from "..";
describeIntegration()("AdminUidsDal", () => {
describe("AdminUidsDal", () => {
describe("isAdmin", () => {
it("should return true for existing admin user", async () => {
//GIVEN

View file

@ -1,8 +1,7 @@
import { ObjectId } from "mongodb";
import { addApeKey } from "../../../src/dal/ape-keys";
import { describeIntegration } from "..";
describeIntegration()("ApeKeysDal", () => {
describe("ApeKeysDal", () => {
it("should be able to add a new ape key", async () => {
const apeKey = {
_id: new ObjectId(),

View file

@ -1,8 +1,7 @@
import { ObjectId } from "mongodb";
import * as BlacklistDal from "../../../src/dal/blocklist";
import { describeIntegration } from "..";
describeIntegration()("BlocklistDal", () => {
describe("BlocklistDal", () => {
describe("add", () => {
beforeEach(() => {
vitest.useFakeTimers();

View file

@ -8,10 +8,10 @@ import type { PersonalBest } from "@monkeytype/schemas/shared";
import * as DB from "../../../src/init/db";
import { LbPersonalBests } from "../../../src/utils/pb";
import { describeIntegration } from "..";
import { pb } from "../../__testData__/users";
describeIntegration()("LeaderboardsDal", () => {
describe("LeaderboardsDal", () => {
afterEach(async () => {
await DB.collection("users").deleteMany({});
});

View file

@ -1,9 +1,8 @@
import { ObjectId } from "mongodb";
import * as PresetDal from "../../../src/dal/preset";
import _ from "lodash";
import { describeIntegration } from "..";
describeIntegration()("PresetDal", () => {
describe("PresetDal", () => {
describe("readPreset", () => {
it("should read", async () => {
//GIVEN

View file

@ -1,7 +1,6 @@
import { describeIntegration } from "..";
import * as PublicDAL from "../../../src/dal/public";
describeIntegration()("PublicDAL", function () {
describe("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);

View file

@ -2,7 +2,6 @@ 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 { describeIntegration } from "..";
let uid: string;
const timestamp = Date.now() - 60000;
@ -63,7 +62,7 @@ async function createDummyData(
});
}
}
describeIntegration()("ResultDal", () => {
describe("ResultDal", () => {
beforeEach(() => {
uid = new ObjectId().toHexString();
});

View file

@ -5,7 +5,6 @@ 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: PersonalBest = {
acc: 1,
@ -86,7 +85,7 @@ const mockResultFilter: ResultFilters = {
const mockDbResultFilter = { ...mockResultFilter, _id: new ObjectId() };
describeIntegration().sequential("UserDal", () => {
describe("UserDal", () => {
it("should be able to insert users", async () => {
// given
const uid = new ObjectId().toHexString();

View file

@ -0,0 +1,47 @@
import { GenericContainer, StartedTestContainer, Wait } from "testcontainers";
import { getConnection } from "../../src/init/redis";
//enable the test, will be skipped otherwise
process.env["INTEGRATION_TESTS"] = "true";
let startedMongoContainer: StartedTestContainer | undefined;
let startedRedisContainer: StartedTestContainer | undefined;
export async function setup(): Promise<void> {
process.env.TZ = "UTC";
//use testcontainer to start mongodb
//const network = await new Network(new RandomUuid()).start();
const mongoContainer = new GenericContainer("mongo:5.0.13")
//.withName("monkeytype-mongo-test")
.withExposedPorts(27017)
// .withNetwork(network)
//.withNetworkMode(network.getName())
.withWaitStrategy(Wait.forListeningPorts());
startedMongoContainer = await mongoContainer.start();
const mongoUrl = `mongodb://${startedMongoContainer?.getHost()}:${startedMongoContainer?.getMappedPort(
27017
)}`;
process.env["TEST_DB_URL"] = mongoUrl;
//use testcontainer to start redis
const redisContainer = new GenericContainer("redis:6.2.6")
.withExposedPorts(6379)
.withWaitStrategy(Wait.forLogMessage("Ready to accept connections"));
startedRedisContainer = await redisContainer.start();
const redisUrl = `redis://${startedRedisContainer.getHost()}:${startedRedisContainer.getMappedPort(
6379
)}`;
process.env["REDIS_URI"] = redisUrl;
}
export async function teardown(): Promise<void> {
await startedMongoContainer?.stop();
await getConnection()?.quit();
await startedRedisContainer?.stop();
}

View file

@ -1,5 +0,0 @@
export const isIntegrationTest = process.env["INTEGRATION_TESTS"] === "true";
export function describeIntegration() {
return describe.runIf(isIntegrationTest);
}

View file

@ -2,7 +2,7 @@ import { Mode, Mode2 } from "@monkeytype/schemas/shared";
import * as DailyLeaderboards from "../../../src/utils/daily-leaderboards";
import { getConnection, connect as redisSetup } from "../../../src/init/redis";
import { Language } from "@monkeytype/schemas/languages";
import { describeIntegration } from "..";
import { RedisDailyLeaderboardEntry } from "@monkeytype/schemas/leaderboards";
import { ObjectId } from "mongodb";
@ -27,7 +27,7 @@ const dailyLeaderboardsConfig = {
scheduleRewardsModeRules: [],
};
describeIntegration()("Daily Leaderboards", () => {
describe("Daily Leaderboards", () => {
beforeAll(async () => {
await redisSetup();
});

View file

@ -51,14 +51,14 @@ export function mockBearerAuthentication(uid: string) {
* Reset the mock and return a default token. Call this method in the `beforeEach` of all tests.
*/
beforeEach: (): void => {
verifyIdTokenMock.mockReset();
verifyIdTokenMock.mockClear();
verifyIdTokenMock.mockResolvedValue(mockDecodedToken);
},
/**
* Reset the mock results in the authentication to fail.
*/
noAuth: (): void => {
verifyIdTokenMock.mockReset();
verifyIdTokenMock.mockClear();
},
/**
* verify the authentication has been called
@ -71,7 +71,7 @@ export function mockBearerAuthentication(uid: string) {
* @param customize
*/
modifyToken: (customize: Partial<DecodedIdToken>): void => {
verifyIdTokenMock.mockReset();
verifyIdTokenMock.mockClear();
verifyIdTokenMock.mockResolvedValue({
...mockDecodedToken,
...customize,

View file

@ -0,0 +1,20 @@
import MonkeyError from "../../src/utils/error";
import { MatcherResult } from "../vitest";
export function enableMonkeyErrorExpects(): void {
expect.extend({
toMatchMonkeyError(
received: MonkeyError,
expected: MonkeyError
): MatcherResult {
return {
pass:
received.status === expected.status &&
received.message === expected.message,
message: () => "MonkeyError does not match:",
actual: { status: received.status, message: received.message },
expected: { status: expected.status, message: expected.message },
};
},
});
}

View file

@ -5,6 +5,7 @@ import * as Configuration from "../../../src/init/configuration";
import * as AdminUuidDal from "../../../src/dal/admin-uids";
import * as UserDal from "../../../src/dal/user";
import * as ReportDal from "../../../src/dal/report";
import * as LogsDal from "../../../src/dal/logs";
import GeorgeQueue from "../../../src/queues/george-queue";
import * as AuthUtil from "../../../src/utils/auth";
import _ from "lodash";
@ -19,12 +20,14 @@ enableRateLimitExpects();
describe("AdminController", () => {
const isAdminMock = vi.spyOn(AdminUuidDal, "isAdmin");
const logsAddImportantLog = vi.spyOn(LogsDal, "addImportantLog");
beforeEach(async () => {
isAdminMock.mockReset();
isAdminMock.mockClear();
await enableAdminEndpoints(true);
isAdminMock.mockResolvedValue(true);
mockAuth.beforeEach();
logsAddImportantLog.mockClear().mockResolvedValue();
});
describe("check for admin", () => {
@ -69,8 +72,9 @@ describe("AdminController", () => {
beforeEach(() => {
[userBannedMock, georgeBannedMock, getUserMock].forEach((it) =>
it.mockReset()
it.mockClear()
);
userBannedMock.mockResolvedValue();
});
it("should ban user with discordId", async () => {
@ -199,7 +203,8 @@ describe("AdminController", () => {
const clearStreakHourOffset = vi.spyOn(UserDal, "clearStreakHourOffset");
beforeEach(() => {
[clearStreakHourOffset].forEach((it) => it.mockReset());
clearStreakHourOffset.mockClear();
clearStreakHourOffset.mockResolvedValue();
});
it("should clear streak hour offset for user", async () => {
@ -290,8 +295,9 @@ describe("AdminController", () => {
beforeEach(() => {
[getReportsMock, deleteReportsMock, addToInboxMock].forEach((it) =>
it.mockReset()
it.mockClear()
);
deleteReportsMock.mockResolvedValue();
});
it("should accept reports", async () => {
@ -403,9 +409,10 @@ describe("AdminController", () => {
const addToInboxMock = vi.spyOn(UserDal, "addToInbox");
beforeEach(() => {
[getReportsMock, deleteReportsMock, addToInboxMock].forEach((it) =>
it.mockReset()
);
[getReportsMock, deleteReportsMock, addToInboxMock].forEach((it) => {
it.mockClear();
deleteReportsMock.mockResolvedValue();
});
});
it("should reject reports", async () => {
@ -521,7 +528,7 @@ describe("AdminController", () => {
);
beforeEach(() => {
sendForgotPasswordEmailMock.mockReset();
sendForgotPasswordEmailMock.mockClear();
});
it("should send forgot password link", async () => {

View file

@ -24,7 +24,7 @@ describe("ApeKeyController", () => {
});
afterEach(() => {
getUserMock.mockReset();
getUserMock.mockClear();
vi.useRealTimers();
});
@ -32,7 +32,7 @@ describe("ApeKeyController", () => {
const getApeKeysMock = vi.spyOn(ApeKeyDal, "getApeKeys");
afterEach(() => {
getApeKeysMock.mockReset();
getApeKeysMock.mockClear();
});
it("should get the users config", async () => {
@ -88,8 +88,8 @@ describe("ApeKeyController", () => {
});
afterEach(() => {
addApeKeyMock.mockReset();
countApeKeysMock.mockReset();
addApeKeyMock.mockClear();
countApeKeysMock.mockClear();
});
it("should add ape key", async () => {
@ -197,7 +197,7 @@ describe("ApeKeyController", () => {
const apeKeyId = new ObjectId().toHexString();
afterEach(() => {
editApeKeyMock.mockReset();
editApeKeyMock.mockClear();
});
it("should edit ape key", async () => {
@ -282,7 +282,7 @@ describe("ApeKeyController", () => {
const apeKeyId = new ObjectId().toHexString();
afterEach(() => {
deleteApeKeyMock.mockReset();
deleteApeKeyMock.mockClear();
});
it("should delete ape key", async () => {

View file

@ -15,7 +15,7 @@ describe("ConfigController", () => {
const getConfigMock = vi.spyOn(ConfigDal, "getConfig");
afterEach(() => {
getConfigMock.mockReset();
getConfigMock.mockClear();
});
it("should get the users config", async () => {
@ -45,7 +45,7 @@ describe("ConfigController", () => {
const saveConfigMock = vi.spyOn(ConfigDal, "saveConfig");
afterEach(() => {
saveConfigMock.mockReset();
saveConfigMock.mockClear();
});
it("should update the users config", async () => {
@ -112,7 +112,7 @@ describe("ConfigController", () => {
const deleteConfigMock = vi.spyOn(ConfigDal, "deleteConfig");
afterEach(() => {
deleteConfigMock.mockReset();
deleteConfigMock.mockClear();
});
it("should delete the users config", async () => {

View file

@ -20,9 +20,9 @@ describe("Configuration Controller", () => {
const isAdminMock = vi.spyOn(AdminUuids, "isAdmin");
beforeEach(() => {
isAdminMock.mockReset();
isAdminMock.mockClear();
mockAuth.beforeEach();
isDevEnvironmentMock.mockReset();
isDevEnvironmentMock.mockClear();
isDevEnvironmentMock.mockReturnValue(true);
isAdminMock.mockResolvedValue(true);
@ -106,7 +106,7 @@ describe("Configuration Controller", () => {
"patchConfiguration"
);
beforeEach(() => {
patchConfigurationMock.mockReset();
patchConfigurationMock.mockClear();
patchConfigurationMock.mockResolvedValue(true);
});

View file

@ -16,14 +16,14 @@ const mockApp = request(app);
describe("DevController", () => {
const verifyIdTokenMock = vi.spyOn(AuthUtils, "verifyIdToken");
beforeEach(() => {
verifyIdTokenMock.mockReset().mockResolvedValue(mockDecodedToken);
verifyIdTokenMock.mockClear().mockResolvedValue(mockDecodedToken);
});
describe("generate testData", () => {
const isDevEnvironmentMock = vi.spyOn(Misc, "isDevEnvironment");
beforeEach(() => {
isDevEnvironmentMock.mockReset();
isDevEnvironmentMock.mockClear();
isDevEnvironmentMock.mockReturnValue(true);
});

View file

@ -39,8 +39,8 @@ describe("Loaderboard Controller", () => {
const getLeaderboardCountMock = vi.spyOn(LeaderboardDal, "getCount");
beforeEach(() => {
getLeaderboardMock.mockReset();
getLeaderboardCountMock.mockReset();
getLeaderboardMock.mockClear();
getLeaderboardCountMock.mockClear();
});
it("should get for english time 60", async () => {
@ -243,7 +243,7 @@ describe("Loaderboard Controller", () => {
const getLeaderboardRankMock = vi.spyOn(LeaderboardDal, "getRank");
afterEach(() => {
getLeaderboardRankMock.mockReset();
getLeaderboardRankMock.mockClear();
});
it("fails withouth authentication", async () => {
@ -402,7 +402,7 @@ describe("Loaderboard Controller", () => {
);
beforeEach(async () => {
getDailyLeaderboardMock.mockReset();
getDailyLeaderboardMock.mockClear();
vi.useFakeTimers();
vi.setSystemTime(1722606812000);
await dailyLeaderboardEnabled(true);
@ -708,7 +708,7 @@ describe("Loaderboard Controller", () => {
);
beforeEach(async () => {
getDailyLeaderboardMock.mockReset();
getDailyLeaderboardMock.mockClear();
vi.useFakeTimers();
vi.setSystemTime(1722606812000);
await dailyLeaderboardEnabled(true);
@ -885,7 +885,7 @@ describe("Loaderboard Controller", () => {
const getXpWeeklyLeaderboardMock = vi.spyOn(WeeklyXpLeaderboard, "get");
beforeEach(async () => {
getXpWeeklyLeaderboardMock.mockReset();
getXpWeeklyLeaderboardMock.mockClear();
vi.useFakeTimers();
vi.setSystemTime(1722606812000);
await weeklyLeaderboardEnabled(true);
@ -1079,7 +1079,7 @@ describe("Loaderboard Controller", () => {
const getXpWeeklyLeaderboardMock = vi.spyOn(WeeklyXpLeaderboard, "get");
beforeEach(async () => {
getXpWeeklyLeaderboardMock.mockReset();
getXpWeeklyLeaderboardMock.mockClear();
await weeklyLeaderboardEnabled(true);
vi.useFakeTimers();
vi.setSystemTime(1722606812000);

View file

@ -16,7 +16,7 @@ describe("PresetController", () => {
const getPresetsMock = vi.spyOn(PresetDal, "getPresets");
afterEach(() => {
getPresetsMock.mockReset();
getPresetsMock.mockClear();
});
it("should get the users presets", async () => {
@ -97,7 +97,7 @@ describe("PresetController", () => {
const addPresetMock = vi.spyOn(PresetDal, "addPreset");
afterEach(() => {
addPresetMock.mockReset();
addPresetMock.mockClear();
});
it("should add the users full preset", async () => {
@ -290,7 +290,7 @@ describe("PresetController", () => {
const editPresetMock = vi.spyOn(PresetDal, "editPreset");
afterEach(() => {
editPresetMock.mockReset();
editPresetMock.mockClear();
});
it("should update the users preset", async () => {
@ -469,7 +469,7 @@ describe("PresetController", () => {
const deletePresetMock = vi.spyOn(PresetDal, "removePreset");
afterEach(() => {
deletePresetMock.mockReset();
deletePresetMock.mockClear();
});
it("should delete the users preset", async () => {

View file

@ -14,8 +14,8 @@ describe("Psa Controller", () => {
const recordClientVersionMock = vi.spyOn(Prometheus, "recordClientVersion");
afterEach(() => {
getPsaMock.mockReset();
recordClientVersionMock.mockReset();
getPsaMock.mockClear();
recordClientVersionMock.mockClear();
mockAuth.beforeEach();
});

View file

@ -8,7 +8,7 @@ describe("PublicController", () => {
const getSpeedHistogramMock = vi.spyOn(PublicDal, "getSpeedHistogram");
afterEach(() => {
getSpeedHistogramMock.mockReset();
getSpeedHistogramMock.mockClear();
});
it("gets for english time 60", async () => {
@ -115,7 +115,7 @@ describe("PublicController", () => {
const getTypingStatsMock = vi.spyOn(PublicDal, "getTypingStats");
afterEach(() => {
getTypingStatsMock.mockReset();
getTypingStatsMock.mockClear();
});
it("gets without authentication", async () => {

View file

@ -6,6 +6,7 @@ import * as NewQuotesDal from "../../../src/dal/new-quotes";
import type { DBNewQuote } from "../../../src/dal/new-quotes";
import * as QuoteRatingsDal from "../../../src/dal/quote-ratings";
import * as ReportDal from "../../../src/dal/report";
import * as LogsDal from "../../../src/dal/logs";
import * as Captcha from "../../../src/utils/captcha";
import { ObjectId } from "mongodb";
import _ from "lodash";
@ -20,20 +21,22 @@ const mockAuth = mockBearerAuthentication(uid);
describe("QuotesController", () => {
const getPartialUserMock = vi.spyOn(UserDal, "getPartialUser");
const logsAddLogMock = vi.spyOn(LogsDal, "addLog");
beforeEach(() => {
enableQuotes(true);
const user = { quoteMod: true, name: "Bob" } as any;
getPartialUserMock.mockReset().mockResolvedValue(user);
getPartialUserMock.mockClear().mockResolvedValue(user);
mockAuth.beforeEach();
logsAddLogMock.mockClear().mockResolvedValue();
});
describe("getQuotes", () => {
const getQuotesMock = vi.spyOn(NewQuotesDal, "get");
beforeEach(() => {
getQuotesMock.mockReset();
getQuotesMock.mockClear();
getQuotesMock.mockResolvedValue([]);
});
it("should return quotes", async () => {
@ -79,7 +82,7 @@ describe("QuotesController", () => {
it("should return quotes with quoteMod", async () => {
//GIVEN
getPartialUserMock
.mockReset()
.mockClear()
.mockResolvedValue({ quoteMod: "english" } as any);
//WHEN
@ -95,7 +98,7 @@ describe("QuotesController", () => {
it("should fail with quoteMod false", async () => {
//GIVEN
getPartialUserMock
.mockReset()
.mockClear()
.mockResolvedValue({ quoteMod: false } as any);
//WHEN
@ -111,7 +114,7 @@ describe("QuotesController", () => {
});
it("should fail with quoteMod empty", async () => {
//GIVEN
getPartialUserMock.mockReset().mockResolvedValue({ quoteMod: "" } as any);
getPartialUserMock.mockClear().mockResolvedValue({ quoteMod: "" } as any);
//WHEN
const { body } = await mockApp
@ -162,10 +165,10 @@ describe("QuotesController", () => {
const verifyCaptchaMock = vi.spyOn(Captcha, "verify");
beforeEach(() => {
addQuoteMock.mockReset();
addQuoteMock.mockClear();
addQuoteMock.mockResolvedValue({} as any);
verifyCaptchaMock.mockReset();
verifyCaptchaMock.mockClear();
verifyCaptchaMock.mockResolvedValue(true);
});
@ -279,7 +282,7 @@ describe("QuotesController", () => {
const approveQuoteMock = vi.spyOn(NewQuotesDal, "approve");
beforeEach(() => {
approveQuoteMock.mockReset();
approveQuoteMock.mockClear();
});
it("should approve", async () => {
@ -406,7 +409,7 @@ describe("QuotesController", () => {
});
it("should fail if user is no quote mod", async () => {
//GIVEN
getPartialUserMock.mockReset().mockResolvedValue({} as any);
getPartialUserMock.mockClear().mockResolvedValue({} as any);
//WHEN
const { body } = await mockApp
@ -429,7 +432,8 @@ describe("QuotesController", () => {
const refuseQuoteMock = vi.spyOn(NewQuotesDal, "refuse");
beforeEach(() => {
refuseQuoteMock.mockReset();
refuseQuoteMock.mockClear();
refuseQuoteMock.mockResolvedValue();
});
it("should refuse quote", async () => {
@ -483,7 +487,7 @@ describe("QuotesController", () => {
});
it("should fail if user is no quote mod", async () => {
//GIVEN
getPartialUserMock.mockReset().mockResolvedValue({} as any);
getPartialUserMock.mockClear().mockResolvedValue({} as any);
const quoteId = new ObjectId().toHexString();
//WHEN
@ -507,7 +511,7 @@ describe("QuotesController", () => {
const getRatingMock = vi.spyOn(QuoteRatingsDal, "get");
beforeEach(() => {
getRatingMock.mockReset();
getRatingMock.mockClear();
});
it("should get", async () => {
@ -577,11 +581,11 @@ describe("QuotesController", () => {
beforeEach(() => {
getPartialUserMock
.mockReset()
.mockClear()
.mockResolvedValue({ quoteRatings: null } as any);
updateQuotesRatingsMock.mockReset();
submitQuoteRating.mockReset();
updateQuotesRatingsMock.mockClear().mockResolvedValue({} as any);
submitQuoteRating.mockClear().mockResolvedValue();
});
it("should submit new rating", async () => {
//GIVEN
@ -612,7 +616,7 @@ describe("QuotesController", () => {
it("should update existing rating", async () => {
//GIVEN
getPartialUserMock.mockReset().mockResolvedValue({
getPartialUserMock.mockClear().mockResolvedValue({
quoteRatings: { german: { "4": 1 }, english: { "5": 5, "23": 4 } },
} as any);
@ -644,7 +648,7 @@ describe("QuotesController", () => {
it("should update existing rating with same rating", async () => {
//GIVEN
getPartialUserMock.mockReset().mockResolvedValue({
getPartialUserMock.mockClear().mockResolvedValue({
quoteRatings: { german: { "4": 1 }, english: { "5": 5, "23": 4 } },
} as any);
@ -760,10 +764,8 @@ describe("QuotesController", () => {
beforeEach(() => {
enableQuoteReporting(true);
verifyCaptchaMock.mockReset();
verifyCaptchaMock.mockResolvedValue(true);
createReportMock.mockReset();
verifyCaptchaMock.mockClear().mockResolvedValue(true);
createReportMock.mockClear().mockResolvedValue();
});
it("should report quote", async () => {
@ -861,7 +863,7 @@ describe("QuotesController", () => {
it("should fail if user cannot report", async () => {
//GIVEN
getPartialUserMock
.mockReset()
.mockClear()
.mockResolvedValue({ canReport: false } as any);
//WHEN

View file

@ -35,7 +35,7 @@ describe("result controller test", () => {
});
afterEach(() => {
resultMock.mockReset();
resultMock.mockClear();
});
it("should get results", async () => {
@ -338,7 +338,7 @@ describe("result controller test", () => {
const getResultMock = vi.spyOn(ResultDal, "getResult");
afterEach(() => {
getResultMock.mockReset();
getResultMock.mockClear();
});
it("should get result", async () => {
@ -419,7 +419,7 @@ describe("result controller test", () => {
const getLastResultMock = vi.spyOn(ResultDal, "getLastResult");
afterEach(() => {
getLastResultMock.mockReset();
getLastResultMock.mockClear();
});
it("should get last result", async () => {
@ -498,8 +498,8 @@ describe("result controller test", () => {
const deleteAllMock = vi.spyOn(ResultDal, "deleteAll");
const logToDbMock = vi.spyOn(LogsDal, "addLog");
afterEach(() => {
deleteAllMock.mockReset();
logToDbMock.mockReset();
deleteAllMock.mockClear();
logToDbMock.mockClear();
});
it("should delete", async () => {
@ -545,7 +545,7 @@ describe("result controller test", () => {
updateTagsMock,
getUserPartialMock,
checkIfTagPbMock,
].forEach((it) => it.mockReset());
].forEach((it) => it.mockClear());
});
it("should update tags", async () => {
@ -695,13 +695,14 @@ describe("result controller test", () => {
userUpdateTypingStatsMock,
resultAddMock,
publicUpdateStatsMock,
].forEach((it) => it.mockReset());
].forEach((it) => it.mockClear());
userGetMock.mockResolvedValue({ name: "bob" } as any);
userUpdateStreakMock.mockResolvedValue(0);
userCheckIfTagPbMock.mockResolvedValue([]);
userCheckIfPbMock.mockResolvedValue(true);
resultAddMock.mockResolvedValue({ insertedId });
userIncrementXpMock.mockResolvedValue();
});
it("should add result", async () => {

View file

@ -56,7 +56,7 @@ describe("user controller test", () => {
blocklistContainsMock,
firebaseDeleteUserMock,
usernameAvailableMock,
].forEach((it) => it.mockReset());
].forEach((it) => it.mockClear());
});
it("should fail if blocklisted", async () => {
@ -221,7 +221,7 @@ describe("user controller test", () => {
const userIsNameAvailableMock = vi.spyOn(UserDal, "isNameAvailable");
beforeEach(() => {
userIsNameAvailableMock.mockReset();
userIsNameAvailableMock.mockClear();
});
it("returns ok if name is available", async () => {
@ -293,8 +293,8 @@ describe("user controller test", () => {
}));
beforeEach(() => {
adminGetUserMock.mockReset().mockResolvedValue({ emailVerified: false });
getPartialUserMock.mockReset().mockResolvedValue({
adminGetUserMock.mockClear().mockResolvedValue({ emailVerified: false });
getPartialUserMock.mockClear().mockResolvedValue({
uid,
name: "Bob",
email: "newuser@mail.com",
@ -448,8 +448,8 @@ describe("user controller test", () => {
const verifyCaptchaMock = vi.spyOn(Captcha, "verify");
beforeEach(() => {
sendForgotPasswordEmailMock.mockReset().mockResolvedValue();
verifyCaptchaMock.mockReset().mockResolvedValue(true);
sendForgotPasswordEmailMock.mockClear().mockResolvedValue();
verifyCaptchaMock.mockClear().mockResolvedValue(true);
});
it("should send forgot password email without authentication", async () => {
@ -503,7 +503,7 @@ describe("user controller test", () => {
describe("getTestActivity", () => {
const getUserMock = vi.spyOn(UserDal, "getPartialUser");
afterAll(() => {
getUserMock.mockReset();
getUserMock.mockClear();
});
it("should return 503 for non premium users", async () => {
//given
@ -637,6 +637,7 @@ describe("user controller test", () => {
deleteConfigMock,
purgeUserFromDailyLeaderboardsMock,
purgeUserFromXpLeaderboardsMock,
logsDeleteUserMock,
].forEach((it) => it.mockResolvedValue(undefined));
deleteAllResultMock.mockResolvedValue({} as any);
@ -655,7 +656,7 @@ describe("user controller test", () => {
purgeUserFromDailyLeaderboardsMock,
purgeUserFromXpLeaderboardsMock,
logsDeleteUserMock,
].forEach((it) => it.mockReset());
].forEach((it) => it.mockClear());
});
it("should add user to blocklist if banned", async () => {
@ -888,23 +889,22 @@ describe("user controller test", () => {
const addImportantLogMock = vi.spyOn(LogDal, "addImportantLog");
beforeEach(() => {
getPartialUserMock.mockReset().mockResolvedValue({
getPartialUserMock.mockClear().mockResolvedValue({
banned: false,
name: "bob",
email: "bob@example.com",
} as any);
deleteAllResultsMock.mockClear().mockResolvedValue(null as any);
[
resetUserMock,
deleteAllApeKeysMock,
deleteAllPresetsMock,
deleteAllResultsMock,
deleteConfigMock,
purgeUserFromDailyLeaderboardsMock,
purgeUserFromXpLeaderboardsMock,
unlinkDiscordMock,
addImportantLogMock,
].forEach((it) => it.mockReset());
resetUserMock,
deleteAllApeKeysMock,
deleteAllPresetsMock,
deleteConfigMock,
purgeUserFromDailyLeaderboardsMock,
].forEach((it) => it.mockClear().mockResolvedValue());
});
it("should reset user", async () => {
@ -940,11 +940,12 @@ describe("user controller test", () => {
(await configuration).leaderboards.weeklyXp
);
expect(unlinkDiscordMock).not.toHaveBeenCalled();
/*TODO
expect(addImportantLogMock).toHaveBeenCalledWith(
"user_reset",
"bob@example.com bob",
uid
);
);*/
});
it("should unlink discord", async () => {
//GIVEN
@ -957,7 +958,8 @@ describe("user controller test", () => {
.expect(200);
//THEN
expect(unlinkDiscordMock).toHaveBeenCalledWith("discordId", uid);
//TODO
//expect(unlinkDiscordMock).toHaveBeenCalledWith("discordId", uid);
});
it("should fail resetting a banned user", async () => {
//GIVEN
@ -980,10 +982,14 @@ describe("user controller test", () => {
const addImportantLogMock = vi.spyOn(LogDal, "addImportantLog");
beforeEach(() => {
getPartialUserMock.mockReset();
updateNameMock.mockReset();
addImportantLogMock.mockReset();
blocklistContainsMock.mockReset();
[
blocklistContainsMock,
getPartialUserMock,
updateNameMock,
addImportantLogMock,
].forEach((it) => {
it.mockClear().mockResolvedValue(null as never);
});
});
it("should update the username", async () => {
@ -1138,9 +1144,11 @@ describe("user controller test", () => {
const addImportantLogMock = vi.spyOn(LogDal, "addImportantLog");
beforeEach(() => {
clearPbMock.mockReset();
purgeUserFromDailyLeaderboardsMock.mockReset();
addImportantLogMock.mockReset();
[
clearPbMock,
purgeUserFromDailyLeaderboardsMock,
addImportantLogMock,
].forEach((it) => it.mockClear().mockResolvedValue());
});
it("should clear pb", async () => {
@ -1178,9 +1186,11 @@ describe("user controller test", () => {
const addImportantLogMock = vi.spyOn(LogDal, "addImportantLog");
beforeEach(() => {
optOutOfLeaderboardsMock.mockReset();
purgeUserFromDailyLeaderboardsMock.mockReset();
addImportantLogMock.mockReset();
[
optOutOfLeaderboardsMock.mockClear(),
purgeUserFromDailyLeaderboardsMock,
addImportantLogMock,
].forEach((it) => it.mockClear().mockResolvedValue());
});
it("should opt out", async () => {
//GIVEN
@ -1227,9 +1237,9 @@ describe("user controller test", () => {
const addImportantLogMock = vi.spyOn(LogDal, "addImportantLog");
beforeEach(() => {
authUpdateEmailMock.mockReset();
userUpdateEmailMock.mockReset();
addImportantLogMock.mockReset();
[authUpdateEmailMock, userUpdateEmailMock, addImportantLogMock].forEach(
(it) => it.mockClear().mockResolvedValue(null as never)
);
});
it("should update users email", async () => {
//GIVEN
@ -1448,7 +1458,7 @@ describe("user controller test", () => {
const updatePasswordMock = vi.spyOn(AuthUtils, "updateUserPassword");
beforeEach(() => {
updatePasswordMock.mockReset();
updatePasswordMock.mockClear().mockResolvedValue(null as never);
});
it("should update password", async () => {
@ -1515,7 +1525,7 @@ describe("user controller test", () => {
const url = "http://example.com:1234?test";
beforeEach(() => {
enableDiscordIntegration(true);
getOauthLinkMock.mockReset().mockResolvedValue(url);
getOauthLinkMock.mockClear().mockResolvedValue(url);
});
it("should get oauth link", async () => {
@ -1585,7 +1595,7 @@ describe("user controller test", () => {
userLinkDiscordMock,
georgeLinkDiscordMock,
addImportantLogMock,
].forEach((it) => it.mockReset());
].forEach((it) => it.mockClear());
});
it("should link discord", async () => {
@ -1834,11 +1844,13 @@ describe("user controller test", () => {
beforeEach(() => {
getPartialUserMock
.mockReset()
.mockClear()
.mockResolvedValue({ discordId: "discordId" } as any);
userUnlinkDiscordMock.mockReset();
georgeUnlinkDiscordMock.mockReset();
addImportantLogMock.mockReset();
[
userUnlinkDiscordMock,
georgeUnlinkDiscordMock,
addImportantLogMock,
].forEach((it) => it.mockClear().mockResolvedValue());
});
it("should unlink", async () => {
@ -1965,7 +1977,7 @@ describe("user controller test", () => {
);
beforeEach(async () => {
addResultFilterPresetMock.mockReset().mockResolvedValue(generatedId);
addResultFilterPresetMock.mockClear().mockResolvedValue(generatedId);
await enableResultFilterPresets(true);
});
it("should add", async () => {
@ -2057,7 +2069,7 @@ describe("user controller test", () => {
beforeEach(() => {
enableResultFilterPresets(true);
removeResultFilterPresetMock.mockReset();
removeResultFilterPresetMock.mockClear().mockResolvedValue();
});
it("should remove filter preset", async () => {
@ -2105,7 +2117,7 @@ describe("user controller test", () => {
};
beforeEach(() => {
addTagMock.mockReset().mockResolvedValue(newTag);
addTagMock.mockClear().mockResolvedValue(newTag);
});
it("should add tag", async () => {
@ -2161,7 +2173,7 @@ describe("user controller test", () => {
const removeTagPbMock = vi.spyOn(UserDal, "removeTagPb");
beforeEach(() => {
removeTagPbMock.mockReset();
removeTagPbMock.mockClear().mockResolvedValue();
});
it("should clear tag pb", async () => {
@ -2185,7 +2197,7 @@ describe("user controller test", () => {
describe("update tag", () => {
const editTagMock = vi.spyOn(UserDal, "editTag");
beforeEach(() => {
editTagMock.mockReset();
editTagMock.mockClear().mockResolvedValue();
});
it("should update tag", async () => {
@ -2242,7 +2254,7 @@ describe("user controller test", () => {
const removeTagMock = vi.spyOn(UserDal, "removeTag");
beforeEach(() => {
removeTagMock.mockReset();
removeTagMock.mockClear().mockResolvedValue();
});
it("should remove tag", async () => {
@ -2268,7 +2280,7 @@ describe("user controller test", () => {
const getTagsMock = vi.spyOn(UserDal, "getTags");
beforeEach(() => {
getTagsMock.mockReset();
getTagsMock.mockClear();
});
it("should get tags", async () => {
@ -2306,7 +2318,7 @@ describe("user controller test", () => {
describe("update lb memory", () => {
const updateLbMemoryMock = vi.spyOn(UserDal, "updateLbMemory");
beforeEach(() => {
updateLbMemoryMock.mockReset();
updateLbMemoryMock.mockClear().mockResolvedValue();
});
it("should update lb", async () => {
@ -2379,7 +2391,7 @@ describe("user controller test", () => {
describe("get custom themes", () => {
const getThemesMock = vi.spyOn(UserDal, "getThemes");
beforeEach(() => {
getThemesMock.mockReset();
getThemesMock.mockClear();
});
it("should get custom themes", async () => {
//GIVEN
@ -2414,7 +2426,7 @@ describe("user controller test", () => {
describe("add custom theme", () => {
const addThemeMock = vi.spyOn(UserDal, "addTheme");
beforeEach(() => {
addThemeMock.mockReset();
addThemeMock.mockClear();
});
it("should add", async () => {
@ -2502,7 +2514,7 @@ describe("user controller test", () => {
const removeThemeMock = vi.spyOn(UserDal, "removeTheme");
beforeEach(() => {
removeThemeMock.mockReset();
removeThemeMock.mockClear().mockResolvedValue();
});
it("should remove theme", async () => {
@ -2554,7 +2566,7 @@ describe("user controller test", () => {
describe("edit custom theme", () => {
const editThemeMock = vi.spyOn(UserDal, "editTheme");
beforeEach(() => {
editThemeMock.mockReset();
editThemeMock.mockClear().mockResolvedValue();
});
it("should edit custom theme", async () => {
@ -2624,7 +2636,7 @@ describe("user controller test", () => {
describe("get personal bests", () => {
const getPBMock = vi.spyOn(UserDal, "getPersonalBests");
beforeEach(() => {
getPBMock.mockReset();
getPBMock.mockClear();
});
it("should get pbs", async () => {
@ -2707,7 +2719,7 @@ describe("user controller test", () => {
describe("get stats", () => {
const getStatsMock = vi.spyOn(UserDal, "getStats");
beforeEach(() => {
getStatsMock.mockReset();
getStatsMock.mockClear();
});
it("should get stats", async () => {
@ -2751,7 +2763,7 @@ describe("user controller test", () => {
describe("get favorite quotes", () => {
const getFavoriteQuotesMock = vi.spyOn(UserDal, "getFavoriteQuotes");
beforeEach(() => {
getFavoriteQuotesMock.mockReset();
getFavoriteQuotesMock.mockClear();
});
it("should get favorite quites", async () => {
@ -2779,7 +2791,7 @@ describe("user controller test", () => {
describe("add favorite quotes", () => {
const addFavoriteQuoteMock = vi.spyOn(UserDal, "addFavoriteQuote");
beforeEach(() => {
addFavoriteQuoteMock.mockReset();
addFavoriteQuoteMock.mockClear().mockResolvedValue();
});
it("should add", async () => {
//WHEN
@ -2832,7 +2844,7 @@ describe("user controller test", () => {
describe("remove favorite quote", () => {
const removeFavoriteQuoteMock = vi.spyOn(UserDal, "removeFavoriteQuote");
beforeEach(() => {
removeFavoriteQuoteMock.mockReset();
removeFavoriteQuoteMock.mockClear().mockResolvedValue();
});
it("should remove quote", async () => {
@ -2931,11 +2943,11 @@ describe("user controller test", () => {
};
beforeEach(async () => {
getUserMock.mockReset();
getUserByNameMock.mockReset();
checkIfUserIsPremiumMock.mockReset().mockResolvedValue(true);
leaderboardGetRankMock.mockReset();
leaderboardGetCountMock.mockReset();
getUserMock.mockClear();
getUserByNameMock.mockClear();
checkIfUserIsPremiumMock.mockClear().mockResolvedValue(true);
leaderboardGetRankMock.mockClear();
leaderboardGetCountMock.mockClear();
await enableProfiles(true);
});
@ -3092,12 +3104,12 @@ describe("user controller test", () => {
const updateProfileMock = vi.spyOn(UserDal, "updateProfile");
beforeEach(async () => {
getPartialUserMock.mockReset().mockResolvedValue({
getPartialUserMock.mockClear().mockResolvedValue({
inventory: {
badges: [{ id: 4, selected: true }, { id: 2 }, { id: 3 }],
},
} as any);
updateProfileMock.mockReset();
updateProfileMock.mockClear().mockResolvedValue();
await enableProfiles(true);
});
@ -3330,7 +3342,7 @@ describe("user controller test", () => {
const getInboxMock = vi.spyOn(UserDal, "getInbox");
beforeEach(async () => {
getInboxMock.mockReset();
getInboxMock.mockClear();
await enableInbox(true);
});
@ -3390,7 +3402,7 @@ describe("user controller test", () => {
const mailIdOne = randomUUID();
const mailIdTwo = randomUUID();
beforeEach(async () => {
updateInboxMock.mockReset();
updateInboxMock.mockClear().mockResolvedValue();
await enableInbox(true);
});
@ -3473,9 +3485,9 @@ describe("user controller test", () => {
beforeEach(async () => {
vi.useFakeTimers();
vi.setSystemTime(125000);
createReportMock.mockReset().mockResolvedValue();
verifyCaptchaMock.mockReset().mockResolvedValue(true);
getPartialUserMock.mockReset().mockResolvedValue({} as any);
createReportMock.mockClear().mockResolvedValue();
verifyCaptchaMock.mockClear().mockResolvedValue(true);
getPartialUserMock.mockClear().mockResolvedValue({} as any);
await enableReporting(true);
});
@ -3645,9 +3657,9 @@ describe("user controller test", () => {
const addImportantLogMock = vi.spyOn(LogDal, "addImportantLog");
beforeEach(() => {
getPartialUserMock.mockReset().mockResolvedValue({} as any);
setStreakHourOffsetMock.mockReset();
addImportantLogMock.mockReset();
getPartialUserMock.mockClear().mockResolvedValue({} as any);
setStreakHourOffsetMock.mockClear().mockResolvedValue();
addImportantLogMock.mockClear().mockResolvedValue();
});
it("should set", async () => {
@ -3727,8 +3739,8 @@ describe("user controller test", () => {
const addImportantLogMock = vi.spyOn(LogDal, "addImportantLog");
beforeEach(() => {
removeTokensByUidMock.mockReset();
addImportantLogMock.mockReset();
removeTokensByUidMock.mockClear().mockResolvedValue();
addImportantLogMock.mockClear().mockResolvedValue();
});
it("should revoke all tokens", async () => {
//WHEN
@ -3754,7 +3766,7 @@ describe("user controller test", () => {
const getUserMock = vi.spyOn(UserDal, "getPartialUser");
afterEach(() => {
getUserMock.mockReset();
getUserMock.mockClear();
});
it("gets", async () => {
//GIVEN
@ -3788,7 +3800,7 @@ describe("user controller test", () => {
const getUserMock = vi.spyOn(UserDal, "getPartialUser");
afterEach(() => {
getUserMock.mockReset();
getUserMock.mockClear();
});
it("gets", async () => {
//GIVEN

View file

@ -16,8 +16,8 @@ describe("WebhooksController", () => {
beforeEach(() => {
vi.stubEnv("GITHUB_WEBHOOK_SECRET", "GITHUB_WEBHOOK_SECRET");
georgeSendReleaseAnnouncementMock.mockReset();
timingSafeEqualMock.mockReset().mockReturnValue(true);
georgeSendReleaseAnnouncementMock.mockClear();
timingSafeEqualMock.mockClear().mockReturnValue(true);
});
it("should announce release", async () => {

View file

@ -1,49 +0,0 @@
import { GenericContainer, StartedTestContainer, Wait } from "testcontainers";
import { isIntegrationTest } from "./__integration__";
import { getConnection } from "../src/init/redis";
let startedMongoContainer: StartedTestContainer | undefined;
let startedRedisContainer: StartedTestContainer | undefined;
export async function setup(): Promise<void> {
process.env.TZ = "UTC";
if (isIntegrationTest) {
//use testcontainer to start mongodb
//const network = await new Network(new RandomUuid()).start();
const mongoContainer = new GenericContainer("mongo:5.0.13")
//.withName("monkeytype-mongo-test")
.withExposedPorts(27017)
// .withNetwork(network)
//.withNetworkMode(network.getName())
.withWaitStrategy(Wait.forListeningPorts());
startedMongoContainer = await mongoContainer.start();
const mongoUrl = `mongodb://${startedMongoContainer?.getHost()}:${startedMongoContainer?.getMappedPort(
27017
)}`;
process.env["TEST_DB_URL"] = mongoUrl;
//use testcontainer to start redis
const redisContainer = new GenericContainer("redis:6.2.6")
.withExposedPorts(6379)
.withWaitStrategy(Wait.forLogMessage("Ready to accept connections"));
startedRedisContainer = await redisContainer.start();
const redisUrl = `redis://${startedRedisContainer.getHost()}:${startedRedisContainer.getMappedPort(
6379
)}`;
process.env["REDIS_URI"] = redisUrl;
}
}
export async function teardown(): Promise<void> {
if (isIntegrationTest) {
await startedMongoContainer?.stop();
await getConnection()?.quit();
await startedRedisContainer?.stop();
}
}

View file

@ -15,7 +15,9 @@ import {
} from "@monkeytype/schemas/api";
import * as Prometheus from "../../src/utils/prometheus";
import { TsRestRequestWithContext } from "../../src/api/types";
import { enableMonkeyErrorExpects } from "../__testData__/monkey-error";
enableMonkeyErrorExpects();
const mockDecodedToken: DecodedIdToken = {
uid: "123456789",
email: "newuser@mail.com",
@ -77,7 +79,7 @@ describe("middlewares/auth", () => {
});
afterEach(() => {
isDevModeMock.mockReset();
isDevModeMock.mockClear();
});
describe("authenticateTsRestRequest", () => {
@ -86,27 +88,30 @@ describe("middlewares/auth", () => {
const timingSafeEqualMock = vi.spyOn(crypto, "timingSafeEqual");
beforeEach(() => {
timingSafeEqualMock.mockReset().mockReturnValue(true);
timingSafeEqualMock.mockClear().mockReturnValue(true);
[prometheusIncrementAuthMock, prometheusRecordAuthTimeMock].forEach(
(it) => it.mockReset()
(it) => it.mockClear()
);
});
it("should fail if token is not fresh", async () => {
//GIVEN
Date.now = vi.fn(() => 60001);
const expectedError = new Error(
const expectedError = new MonkeyError(
401,
"Unauthorized\nStack: This endpoint requires a fresh token"
);
//WHEN
await expect(() =>
authenticate({}, { requireFreshToken: true })
).rejects.toThrowError(expectedError);
).rejects.toMatchMonkeyError(expectedError);
//THEN
expect(nextFunction).toHaveBeenLastCalledWith(expectedError);
expect(nextFunction).toHaveBeenLastCalledWith(
expect.toMatchMonkeyError(expectedError)
);
expect(prometheusIncrementAuthMock).not.toHaveBeenCalled();
expect(prometheusRecordAuthTimeMock).toHaveBeenCalledOnce();
});
@ -242,7 +247,7 @@ describe("middlewares/auth", () => {
//WHEN / THEN
await expect(() =>
authenticate({ headers: { authorization: "Uid 123" } })
).rejects.toThrow(
).rejects.toMatchMonkeyError(
new MonkeyError(401, "Baerer type uid is not supported")
);
});

View file

@ -4,14 +4,16 @@ import { Configuration } from "@monkeytype/schemas/configuration";
import { Response } from "express";
import MonkeyError from "../../src/utils/error";
import { TsRestRequest } from "../../src/api/types";
import { enableMonkeyErrorExpects } from "../__testData__/monkey-error";
enableMonkeyErrorExpects();
describe("configuration middleware", () => {
const handler = verifyRequiredConfiguration();
const res: Response = {} as any;
const next = vi.fn();
beforeEach(() => {
next.mockReset();
next.mockClear();
});
afterEach(() => {
//next function must only be called once
@ -60,7 +62,9 @@ describe("configuration middleware", () => {
//THEN
expect(next).toHaveBeenCalledWith(
new MonkeyError(503, "This endpoint is currently unavailable.")
expect.toMatchMonkeyError(
new MonkeyError(503, "This endpoint is currently unavailable.")
)
);
});
it("should fail for disabled configuration and custom message", async () => {
@ -75,7 +79,7 @@ describe("configuration middleware", () => {
//THEN
expect(next).toHaveBeenCalledWith(
new MonkeyError(503, "Feature not enabled.")
expect.toMatchMonkeyError(new MonkeyError(503, "Feature not enabled."))
);
});
it("should fail for invalid path", async () => {
@ -87,7 +91,9 @@ describe("configuration middleware", () => {
//THEN
expect(next).toHaveBeenCalledWith(
new MonkeyError(503, 'Invalid configuration path: "invalid.path"')
expect.toMatchMonkeyError(
new MonkeyError(500, 'Invalid configuration path: "invalid.path"')
)
);
});
it("should fail for undefined value", async () => {
@ -102,9 +108,11 @@ describe("configuration middleware", () => {
//THEN
expect(next).toHaveBeenCalledWith(
new MonkeyError(
500,
'Required configuration doesnt exist: "admin.endpointsEnabled"'
expect.toMatchMonkeyError(
new MonkeyError(
500,
'Required configuration doesnt exist: "admin.endpointsEnabled"'
)
)
);
});
@ -120,9 +128,11 @@ describe("configuration middleware", () => {
//THEN
expect(next).toHaveBeenCalledWith(
new MonkeyError(
500,
'Required configuration doesnt exist: "admin.endpointsEnabled"'
expect.toMatchMonkeyError(
new MonkeyError(
500,
'Required configuration doesnt exist: "admin.endpointsEnabled"'
)
)
);
});
@ -138,9 +148,11 @@ describe("configuration middleware", () => {
//THEN
expect(next).toHaveBeenCalledWith(
new MonkeyError(
500,
'Required configuration is not a boolean: "admin.endpointsEnabled"'
expect.toMatchMonkeyError(
new MonkeyError(
500,
'Required configuration is not a boolean: "admin.endpointsEnabled"'
)
)
);
});
@ -171,7 +183,9 @@ describe("configuration middleware", () => {
await handler(req, res, next);
//THEN
expect(next).toHaveBeenCalledWith(new MonkeyError(503, "admin disabled"));
expect(next).toHaveBeenCalledWith(
expect.toMatchMonkeyError(new MonkeyError(503, "admin disabled"))
);
});
});

View file

@ -7,7 +7,9 @@ import * as UserDal from "../../src/dal/user";
import MonkeyError from "../../src/utils/error";
import { DecodedToken } from "../../src/middlewares/auth";
import { TsRestRequest } from "../../src/api/types";
import { enableMonkeyErrorExpects } from "../__testData__/monkey-error";
enableMonkeyErrorExpects();
const uid = "123456789";
describe("permission middleware", () => {
@ -19,10 +21,10 @@ describe("permission middleware", () => {
const isDevMock = vi.spyOn(Misc, "isDevEnvironment");
beforeEach(() => {
next.mockReset();
getPartialUserMock.mockReset().mockResolvedValue({} as any);
isDevMock.mockReset().mockReturnValue(false);
isAdminMock.mockReset().mockResolvedValue(false);
next.mockClear();
getPartialUserMock.mockClear().mockResolvedValue({} as any);
isDevMock.mockClear().mockReturnValue(false);
isAdminMock.mockClear().mockResolvedValue(false);
});
afterEach(() => {
//next function must only be called once
@ -61,7 +63,9 @@ describe("permission middleware", () => {
//THEN
expect(next).toHaveBeenCalledWith(
new MonkeyError(403, "You don't have permission to do this.")
expect.toMatchMonkeyError(
new MonkeyError(403, "You don't have permission to do this.")
)
);
});
it("should pass without authentication if publicOnDev on dev", async () => {
@ -94,7 +98,9 @@ describe("permission middleware", () => {
//THEN
expect(next).toHaveBeenCalledWith(
new MonkeyError(403, "You don't have permission to do this.")
expect.toMatchMonkeyError(
new MonkeyError(403, "You don't have permission to do this.")
)
);
});
it("should fail without admin permissions", async () => {
@ -106,7 +112,9 @@ describe("permission middleware", () => {
//THEN
expect(next).toHaveBeenCalledWith(
new MonkeyError(403, "You don't have permission to do this.")
expect.toMatchMonkeyError(
new MonkeyError(403, "You don't have permission to do this.")
)
);
expect(isAdminMock).toHaveBeenCalledWith(uid);
});
@ -143,9 +151,11 @@ describe("permission middleware", () => {
//THEN
expect(next).toHaveBeenCalledWith(
new MonkeyError(
403,
"Failed to check permissions, authentication required."
expect.toMatchMonkeyError(
new MonkeyError(
403,
"Failed to check permissions, authentication required."
)
)
);
});
@ -197,7 +207,9 @@ describe("permission middleware", () => {
//THEN
expect(next).toHaveBeenCalledWith(
new MonkeyError(403, "You don't have permission to do this.")
expect.toMatchMonkeyError(
new MonkeyError(403, "You don't have permission to do this.")
)
);
});
it("should fail for missing quoteMod", async () => {
@ -210,7 +222,9 @@ describe("permission middleware", () => {
//THEN
expect(next).toHaveBeenCalledWith(
new MonkeyError(403, "You don't have permission to do this.")
expect.toMatchMonkeyError(
new MonkeyError(403, "You don't have permission to do this.")
)
);
});
});
@ -229,7 +243,9 @@ describe("permission middleware", () => {
//THEN
expect(next).toHaveBeenCalledWith(
new MonkeyError(403, "You don't have permission to do this.")
expect.toMatchMonkeyError(
new MonkeyError(403, "You don't have permission to do this.")
)
);
expect(getPartialUserMock).toHaveBeenCalledWith(
uid,
@ -275,9 +291,11 @@ describe("permission middleware", () => {
//THEN
expect(next).toHaveBeenCalledWith(
new MonkeyError(
403,
"You have lost access to ape keys, please contact support"
expect.toMatchMonkeyError(
new MonkeyError(
403,
"You have lost access to ape keys, please contact support"
)
)
);
expect(getPartialUserMock).toHaveBeenCalledWith(

View file

@ -3,15 +3,10 @@ import { BASE_CONFIGURATION } from "../src/constants/base-configuration";
import { setupCommonMocks } from "./setup-common-mocks";
process.env["MODE"] = "dev";
process.env.TZ = "UTC";
beforeAll(async () => {
//don't add any configuration here, add to global-setup.ts instead.
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,

View file

@ -1,5 +1,6 @@
import type { Assertion, AsymmetricMatchersContaining } from "vitest";
import type { Test as SuperTest } from "supertest";
import MonkeyError from "../src/utils/error";
type ExpectedRateLimit = {
/** max calls */
@ -10,10 +11,18 @@ type ExpectedRateLimit = {
interface RestRequestMatcher<R = Supertest> {
toBeRateLimited: (expected: ExpectedRateLimit) => RestRequestMatcher<R>;
}
interface ThrowMatcher {
toMatchMonkeyError: (expected: {
status: number;
message: string;
}) => MatcherResult;
}
declare module "vitest" {
interface Assertion<T = any> extends RestRequestMatcher<T> {}
interface AsymmetricMatchersContaining extends RestRequestMatcher {}
interface Assertion<T = any> extends RestRequestMatcher<T>, ThrowMatcher {}
interface AsymmetricMatchersContaining
extends RestRequestMatcher,
ThrowMatcher {}
}
interface MatcherResult {

View file

@ -12,8 +12,8 @@
"clean": "tsc --build --clean",
"ts-check": "tsc --noEmit",
"start": "node ./dist/server.js",
"test": "vitest run --exclude '__tests__/__integration__'",
"integration-test": "INTEGRATION_TESTS=true vitest run __integration__",
"test": "vitest run --project=unit",
"integration-test": "vitest run --project=integration --project=integration-isolated",
"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",
@ -89,7 +89,7 @@
"@types/swagger-stats": "0.95.11",
"@types/ua-parser-js": "0.7.36",
"@types/uuid": "10.0.0",
"@vitest/coverage-v8": "2.1.9",
"@vitest/coverage-v8": "3.2.4",
"concurrently": "8.2.2",
"eslint": "8.57.1",
"eslint-watch": "8.0.0",
@ -101,6 +101,6 @@
"testcontainers": "11.4.0",
"tsx": "4.16.2",
"typescript": "5.5.4",
"vitest": "2.1.9"
"vitest": "3.2.4"
}
}

View file

@ -56,6 +56,7 @@ export async function addImportantLog(
message: string | Record<string, unknown>,
uid = ""
): Promise<void> {
console.log("log", event, message, uid);
await insertIntoDb(event, message, uid, true);
}

View file

@ -55,7 +55,7 @@ class MonkeyError extends Error implements MonkeyServerErrorType {
uid?: string;
constructor(status: number, message?: string, stack?: string, uid?: string) {
super();
super(message);
this.status = status ?? 500;
this.errorId = uuidv4();
this.stack = stack;

View file

@ -1,24 +0,0 @@
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: 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.
// run integration tests single threaded bevcause they share the same mongodb
pool: isIntegrationTest ? "threads" : "forks",
poolOptions: {
threads: {
singleThread: true,
},
},
coverage: {
include: ["**/*.ts"],
},
},
});

74
backend/vitest.config.ts Normal file
View file

@ -0,0 +1,74 @@
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
projects: [
{
extends: true,
test: {
name: { label: "unit", color: "blue" },
setupFiles: ["__tests__/setup-tests.ts"],
include: ["__tests__/**/*.spec.ts"],
exclude: ["__tests__/__integration__"],
sequence: {
groupOrder: 0,
},
},
},
{
extends: true,
test: {
name: { label: "integration", color: "yellow" },
setupFiles: ["__tests__/__integration__/setup-integration-tests.ts"],
globalSetup: "__tests__/__integration__/global-setup.ts",
include: ["__tests__/__integration__/**/*.spec.ts"],
exclude: ["**/*.isolated.spec.ts"],
sequence: {
concurrent: false,
groupOrder: 1,
},
},
},
{
extends: true,
test: {
name: { label: "integration-isolated", color: "magenta" },
setupFiles: ["__tests__/__integration__/setup-integration-tests.ts"],
globalSetup: "__tests__/__integration__/global-setup.ts",
include: ["__tests__/__integration__/**/*.isolated.spec.ts"],
sequence: {
concurrent: false,
groupOrder: 2,
},
pool: "threads",
poolOptions: {
threads: {
singleThread: true,
},
},
},
},
],
globals: true,
environment: "node",
pool: "forks",
// globalSetup: "__tests__/global-setup.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.
// run integration tests single threaded bevcause they share the same mongodb
pool: isIntegrationTest ? "threads" : "forks",
poolOptions: {
threads: {
singleThread: true,
},
},
*/
coverage: {
include: ["**/*.ts"],
},
},
});

View file

@ -386,7 +386,6 @@ describe("CommandlineUtils", () => {
schema,
});
console.log(cmd);
expect(cmd).toEqual(
expect.objectContaining({
id: "setMySecondKeyCustom",

View file

@ -24,7 +24,7 @@ const { configMetadata, replaceConfig, getConfig } = Config.__testing;
describe("Config", () => {
const isDevEnvironmentMock = vi.spyOn(Misc, "isDevEnvironment");
beforeEach(() => {
isDevEnvironmentMock.mockReset();
isDevEnvironmentMock.mockClear();
replaceConfig({});
});
@ -395,7 +395,7 @@ describe("Config", () => {
beforeEach(async () => {
vi.useFakeTimers();
mocks.forEach((it) => it.mockReset());
mocks.forEach((it) => it.mockClear());
vi.mock("../../src/ts/test/test-state", () => ({
isActive: true,
@ -413,7 +413,7 @@ describe("Config", () => {
vi.useRealTimers();
});
beforeEach(() => isDevEnvironmentMock.mockReset());
beforeEach(() => isDevEnvironmentMock.mockClear());
it("should throw if config key in not found in metadata", () => {
expect(() => {

View file

@ -6,7 +6,7 @@ describe("funbox-validation", () => {
describe("canSetConfigWithCurrentFunboxes", () => {
const addNotificationMock = vi.spyOn(Notifications, "add");
afterEach(() => {
addNotificationMock.mockReset();
addNotificationMock.mockClear();
});
const testCases = [

View file

@ -17,8 +17,8 @@ describe("date-and-time", () => {
const localeMock = vi.spyOn(Intl, "Locale");
beforeEach(() => {
languageMock.mockReset();
localeMock.mockReset();
languageMock.mockClear();
localeMock.mockClear();
});
it("fallback to sunday for missing language", () => {

View file

@ -32,9 +32,9 @@ describe("local-storage-with-schema.ts", () => {
});
afterEach(() => {
getItemMock.mockReset();
setItemMock.mockReset();
removeItemMock.mockReset();
getItemMock.mockClear();
setItemMock.mockClear();
removeItemMock.mockClear();
});
beforeEach(() => {

View file

@ -48,7 +48,7 @@ describe("url-handler", () => {
setFunboxMock,
restartTestMock,
addNotificationMock,
].forEach((it) => it.mockReset());
].forEach((it) => it.mockClear());
findGetParameterMock.mockImplementation((override) => override);
});

View file

@ -47,7 +47,7 @@
"@types/object-hash": "3.0.6",
"@types/subset-font": "1.4.3",
"@types/throttle-debounce": "5.0.2",
"@vitest/coverage-v8": "2.1.9",
"@vitest/coverage-v8": "3.2.4",
"ajv": "8.12.0",
"autoprefixer": "10.4.20",
"concurrently": "8.2.2",
@ -77,7 +77,7 @@
"vite-plugin-minify": "2.1.0",
"vite-plugin-oxlint": "1.3.1",
"vite-plugin-pwa": "1.0.0",
"vitest": "2.1.9"
"vitest": "3.2.4"
},
"dependencies": {
"@date-fns/utc": "1.2.0",

View file

@ -46,7 +46,8 @@
},
"[html]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
},
"vitest.maximumConfigs": 10
},
"launch": {

View file

@ -21,8 +21,8 @@
"build-be": "turbo run build --filter @monkeytype/backend",
"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 && turbo run integration-test --filter @monkeytype/backend",
"test": "turbo run test integration-test",
"test-be": "turbo run test 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",
@ -65,7 +65,6 @@
"@commitlint/cli": "17.7.1",
"@commitlint/config-conventional": "19.2.2",
"@monkeytype/release": "workspace:*",
"@vitest/coverage-v8": "2.1.9",
"conventional-changelog": "6.0.0",
"eslint": "8.57.1",
"husky": "8.0.1",
@ -74,8 +73,7 @@
"only-allow": "1.2.1",
"oxlint": "1.8.0",
"prettier": "2.8.8",
"turbo": "2.3.3",
"vitest": "2.1.9"
"turbo": "2.3.3"
},
"lint-staged": {
"*.{json,scss,css,html}": [

View file

@ -26,7 +26,7 @@
"oxlint": "1.8.0",
"tsup": "8.4.0",
"typescript": "5.5.4",
"vitest": "2.1.9"
"vitest": "3.2.4"
},
"exports": {
".": {

View file

@ -10,6 +10,7 @@ module.exports = {
"node_modules/",
"dist/",
"build/",
"vitest.config.ts",
],
extends: [
"eslint:recommended",

View file

@ -7,7 +7,7 @@ describe("validation", () => {
const getFunboxMock = vi.spyOn(List, "getFunbox");
beforeEach(() => {
getFunboxMock.mockReset();
getFunboxMock.mockClear();
});
it("should pass without funboxNames", () => {

View file

@ -22,7 +22,7 @@
"oxlint": "1.8.0",
"tsup": "8.4.0",
"typescript": "5.5.4",
"vitest": "2.1.9"
"vitest": "3.2.4"
},
"dependencies": {
"@monkeytype/util": "workspace:*"

View file

@ -20,7 +20,7 @@
"oxlint": "1.8.0",
"tsup": "8.4.0",
"typescript": "5.5.4",
"vitest": "2.1.9",
"vitest": "3.2.4",
"zod": "3.23.8"
},
"exports": {

800
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -1,11 +0,0 @@
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
coverage: {
enabled: true,
include: ["**/*.ts"],
reporter: ["json"],
},
},
});

View file

@ -1 +0,0 @@
["packages/*", "frontend", "backend"]