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 2b65382fb..3337e35bb 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 daadbbcab..aad7271d2 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 b92a3ae4a..a37c90728 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 1678d5eb9..440cbfe3d 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 418185267..9e7bd5e13 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 b97862a56..ec938216d 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 92b9aedf5..7a372c3b0 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 87834e83c..db46f7c1b 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 d763a06ef..13eb3ecbc 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 000000000..66c81ce32 --- /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 000000000..7ce1af29a --- /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 26c27b5b6..9b6faa31c 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 a7de03ada..7a436bab6 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 8d90dca5e..c24a2b8c8 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 f32d40dbe..27dfe0722 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 64c96042a..d039d97b1 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 000000000..0841e1064 --- /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 68a6d7efc..3ecb35322 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 6e35f2544..6e8726ddc 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 ecea6a5bb..cbaa46b57 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 3e7aaf01a..bf28b2eda 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 8e4b84c06..18bb2173b 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,