From eb44f8b43c049af5e13f67ce17e8ed09f126cebf Mon Sep 17 00:00:00 2001 From: Jeevankumar S <110320697+Jeevankumar-s@users.noreply.github.com> Date: Sat, 21 Feb 2026 08:43:50 +0530 Subject: [PATCH] chore(api): migrate tests to Vitest v4 (#65950) --- api/package.json | 4 +- .../utils/exam-environment.test.ts | 3 +- api/src/routes/helpers/auth-helpers.test.ts | 14 +- api/src/routes/protected/certificate.test.ts | 9 +- api/src/routes/protected/challenge.test.ts | 14 +- api/src/routes/protected/donate.test.ts | 49 +++--- api/src/routes/protected/settings.test.ts | 15 +- api/src/routes/public/donate.test.ts | 46 +++--- pnpm-lock.yaml | 142 ++++++------------ 9 files changed, 125 insertions(+), 171 deletions(-) diff --git a/api/package.json b/api/package.json index 1657ed2c77f..92e6437fd35 100644 --- a/api/package.json +++ b/api/package.json @@ -51,7 +51,7 @@ "@types/nodemailer": "6.4.22", "@types/supertest": "2.0.16", "@types/validator": "13.15.10", - "@vitest/ui": "^3.2.4", + "@vitest/ui": "^4.0.15", "dotenv-cli": "7.4.4", "eslint": "^9.39.1", "eslint-plugin-jsdoc": "48.11.0", @@ -60,7 +60,7 @@ "supertest": "6.3.3", "tsx": "4.21.0", "typescript": "5.9.3", - "vitest": "^3.2.4" + "vitest": "^4.0.15" }, "engines": { "node": ">=24", diff --git a/api/src/exam-environment/utils/exam-environment.test.ts b/api/src/exam-environment/utils/exam-environment.test.ts index 30bf20ede8e..bb6b53c252a 100644 --- a/api/src/exam-environment/utils/exam-environment.test.ts +++ b/api/src/exam-environment/utils/exam-environment.test.ts @@ -1,4 +1,5 @@ import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest'; +import type { MockInstance } from 'vitest'; import { ExamEnvironmentAnswer, ExamEnvironmentQuestionType @@ -29,7 +30,7 @@ import { // generate a valid exam. // Another option is to call `generateExam` hundreds of times in a loop test :shrug: describe('Exam Environment mocked Math.random', () => { - let spy: ReturnType; + let spy: MockInstance; beforeAll(() => { spy = vi.spyOn(Math, 'random').mockReturnValue(0.123456789); }); diff --git a/api/src/routes/helpers/auth-helpers.test.ts b/api/src/routes/helpers/auth-helpers.test.ts index d097a2e66c8..40e1010f604 100644 --- a/api/src/routes/helpers/auth-helpers.test.ts +++ b/api/src/routes/helpers/auth-helpers.test.ts @@ -50,8 +50,8 @@ describe('findOrCreateUser', () => { afterEach(async () => { await fastify.prisma.user.deleteMany({ where: { email } }); await fastify.prisma.dripCampaign.deleteMany({ where: { email } }); - await fastify.close(); - vi.clearAllMocks(); + vi.restoreAllMocks(); + captureException.mockReset(); }); test('should send a message to Sentry if there are multiple users with the same email', async () => { @@ -130,9 +130,10 @@ describe('findOrCreateUser', () => { test('should not prevent user creation if drip campaign record creation fails', async () => { vi.spyOn(fastify.gb, 'isOn').mockImplementationOnce(() => true); - // Mock dripCampaign.create to throw an error - const createSpy = vi - .spyOn(fastify.prisma.dripCampaign, 'create') + const originalCreate = fastify.prisma.dripCampaign.create; + + fastify.prisma.dripCampaign.create = vi + .fn() .mockRejectedValueOnce(new Error('Database error')); const user = await findOrCreateUser(fastify, email); @@ -140,7 +141,6 @@ describe('findOrCreateUser', () => { expect(user).toBeDefined(); expect(user.id).toBeTruthy(); - // Verify error was captured by Sentry expect(captureException).toHaveBeenCalledTimes(1); expect(captureException).toHaveBeenCalledWith( expect.objectContaining({ @@ -148,7 +148,7 @@ describe('findOrCreateUser', () => { }) ); - createSpy.mockRestore(); + fastify.prisma.dripCampaign.create = originalCreate; }); test('should not create drip campaign for existing users', async () => { diff --git a/api/src/routes/protected/certificate.test.ts b/api/src/routes/protected/certificate.test.ts index 21502a35d6a..e7cd9d570b2 100644 --- a/api/src/routes/protected/certificate.test.ts +++ b/api/src/routes/protected/certificate.test.ts @@ -424,11 +424,10 @@ describe('certificate routes', () => { } }); - vi.spyOn(fastifyTestInstance.prisma.user, 'update').mockImplementation( - () => { - throw new Error('test'); - } - ); + vi.spyOn(fastifyTestInstance.prisma, 'user', 'get').mockReturnValue({ + ...fastifyTestInstance.prisma.user, + update: vi.fn().mockRejectedValueOnce(new Error('test')) + }); const response = await superRequest('/certificate/verify', { method: 'PUT', diff --git a/api/src/routes/protected/challenge.test.ts b/api/src/routes/protected/challenge.test.ts index 017e77cf209..cd9a2b2e2c5 100644 --- a/api/src/routes/protected/challenge.test.ts +++ b/api/src/routes/protected/challenge.test.ts @@ -378,11 +378,17 @@ describe('challengeRoutes', () => { // function with undefined when restoring a prisma function (for some // reason) test('Should return an error response if something goes wrong', async () => { + const originalUserToken = fastifyTestInstance.prisma.userToken; + vi.spyOn( - fastifyTestInstance.prisma.userToken, - 'findUnique' - ).mockImplementationOnce(() => { - throw new Error('Database error'); + fastifyTestInstance.prisma, + 'userToken', + 'get' + ).mockReturnValue({ + ...originalUserToken, + findUnique: vi.fn().mockImplementationOnce(() => { + throw new Error('Database error'); + }) }); const tokenResponse = await superPost('/user/user-token'); const token = (tokenResponse.body as { userToken: string }).userToken; diff --git a/api/src/routes/protected/donate.test.ts b/api/src/routes/protected/donate.test.ts index 6dfc789062f..819b5908243 100644 --- a/api/src/routes/protected/donate.test.ts +++ b/api/src/routes/protected/donate.test.ts @@ -124,30 +124,31 @@ const generateMockSubCreate = (status: string) => () => const defaultError = () => Promise.reject(new Error('Stripe encountered an error')); -vi.mock('stripe', () => { - return { - default: vi.fn().mockImplementation(() => { - return { - customers: { - create: mockCustomerCreate, - update: mockCustomerUpdate - }, - paymentMethods: { - attach: mockAttachPaymentMethod - }, - subscriptions: { - create: mockSubCreate, - retrieve: mockSubRetrieve - }, - checkout: { - sessions: { - create: mockCheckoutSessionCreate - } - } - }; - }) - }; -}); +vi.mock('stripe', () => ({ + default: class { + constructor() {} + + customers = { + create: mockCustomerCreate, + update: mockCustomerUpdate + }; + + paymentMethods = { + attach: mockAttachPaymentMethod + }; + + subscriptions = { + create: mockSubCreate, + retrieve: mockSubRetrieve + }; + + checkout = { + sessions: { + create: mockCheckoutSessionCreate + } + }; + } +})); describe('Donate', () => { let setCookies: string[]; diff --git a/api/src/routes/protected/settings.test.ts b/api/src/routes/protected/settings.test.ts index bb42d14a241..216326a7f77 100644 --- a/api/src/routes/protected/settings.test.ts +++ b/api/src/routes/protected/settings.test.ts @@ -602,16 +602,17 @@ Please wait 5 minutes to resend an authentication link.` // function with undefined when restoring a prisma function (for some // reason) test('PUT sends an email to the new email address', async () => { + const originalAuthToken = fastifyTestInstance.prisma.authToken; vi.spyOn( - fastifyTestInstance.prisma.authToken, - 'create' - ).mockImplementationOnce(() => - // @ts-expect-error This is a mock implementation, all we're - // interested in is the id. - Promise.resolve({ + fastifyTestInstance.prisma, + 'authToken', + 'get' + ).mockReturnValue({ + ...originalAuthToken, + create: vi.fn().mockResolvedValue({ id: '123' }) - ); + }); await superPut('/update-my-email').send({ email: unusedEmailOne }); diff --git a/api/src/routes/public/donate.test.ts b/api/src/routes/public/donate.test.ts index 867d9faf300..84b4c833858 100644 --- a/api/src/routes/public/donate.test.ts +++ b/api/src/routes/public/donate.test.ts @@ -66,31 +66,27 @@ const generateMockSubCreate = (status: string) => () => } } }); -vi.mock('stripe', () => { - return { - default: vi.fn().mockImplementation(() => { - return { - customers: { - create: mockCustomerCreate, - update: mockCustomerUpdate - }, - paymentMethods: { - attach: mockAttachPaymentMethod - }, - subscriptions: { - create: mockSubCreate, - retrieve: mockSubRetrieve - }, - checkout: { - sessions: { - create: mockCheckoutSessionCreate - } - } - }; - }) - }; -}); - +vi.mock('stripe', () => ({ + default: class { + constructor() {} + customers = { + create: mockCustomerCreate, + update: mockCustomerUpdate + }; + paymentMethods = { + attach: mockAttachPaymentMethod + }; + subscriptions = { + create: mockSubCreate, + retrieve: mockSubRetrieve + }; + checkout = { + sessions: { + create: mockCheckoutSessionCreate + } + }; + } +})); describe('Donate', () => { let setCookies: string[]; setupServer(); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d6538acb344..bca96b683f3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -217,8 +217,8 @@ importers: specifier: 13.15.10 version: 13.15.10 '@vitest/ui': - specifier: ^3.2.4 - version: 3.2.4(vitest@3.2.4) + specifier: ^4.0.15 + version: 4.0.15(vitest@4.0.15) dotenv-cli: specifier: 7.4.4 version: 7.4.4 @@ -244,8 +244,8 @@ importers: specifier: 5.9.3 version: 5.9.3 vitest: - specifier: ^3.2.4 - version: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.13)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@26.1.0)(msw@2.12.10(@types/node@24.10.13)(typescript@5.9.3))(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.1) + specifier: ^4.0.15 + version: 4.0.15(@opentelemetry/api@1.9.0)(@types/node@24.10.13)(@vitest/ui@4.0.15)(jiti@2.6.1)(jsdom@26.1.0)(msw@2.12.10(@types/node@24.10.13)(typescript@5.9.3))(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.1) client: dependencies: @@ -20769,15 +20769,6 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.0.3 - '@vitest/mocker@3.2.4(msw@2.12.10(@types/node@24.10.13)(typescript@5.9.3))(vite@7.1.3(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.1))': - dependencies: - '@vitest/spy': 3.2.4 - estree-walker: 3.0.3 - magic-string: 0.30.17 - optionalDependencies: - msw: 2.12.10(@types/node@24.10.13)(typescript@5.9.3) - vite: 7.1.3(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.1) - '@vitest/mocker@3.2.4(msw@2.12.10(@types/node@25.2.3)(typescript@5.9.3))(vite@7.1.3(@types/node@25.2.3)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.1))': dependencies: '@vitest/spy': 3.2.4 @@ -20860,7 +20851,7 @@ snapshots: sirv: 3.0.2 tinyglobby: 0.2.14 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.13)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@26.1.0)(msw@2.12.10(@types/node@24.10.13)(typescript@5.9.3))(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.1) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@25.2.3)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@26.1.0)(msw@2.12.10(@types/node@25.2.3)(typescript@5.9.3))(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.1) '@vitest/ui@4.0.15(vitest@4.0.15)': dependencies: @@ -20871,7 +20862,7 @@ snapshots: sirv: 3.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vitest: 4.0.15(@opentelemetry/api@1.9.0)(@types/node@25.2.3)(@vitest/ui@4.0.15)(jiti@2.6.1)(jsdom@26.1.0)(msw@2.12.10(@types/node@25.2.3)(typescript@5.9.3))(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.1) + vitest: 4.0.15(@opentelemetry/api@1.9.0)(@types/node@24.10.13)(@vitest/ui@4.0.15)(jiti@2.6.1)(jsdom@26.1.0)(msw@2.12.10(@types/node@24.10.13)(typescript@5.9.3))(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.1) '@vitest/utils@3.2.4': dependencies: @@ -30436,27 +30427,6 @@ snapshots: unist-util-stringify-position: 2.0.3 vfile-message: 2.0.4 - vite-node@3.2.4(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.1): - dependencies: - cac: 6.7.14 - debug: 4.4.1 - es-module-lexer: 1.7.0 - pathe: 2.0.3 - vite: 7.1.3(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.1) - transitivePeerDependencies: - - '@types/node' - - jiti - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - vite-node@3.2.4(@types/node@25.2.3)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.1): dependencies: cac: 6.7.14 @@ -30478,22 +30448,6 @@ snapshots: - tsx - yaml - vite@7.1.3(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.1): - dependencies: - esbuild: 0.25.9 - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 - postcss: 8.5.6 - rollup: 4.46.2 - tinyglobby: 0.2.14 - optionalDependencies: - '@types/node': 24.10.13 - fsevents: 2.3.3 - jiti: 2.6.1 - terser: 5.46.0 - tsx: 4.21.0 - yaml: 2.8.1 - vite@7.1.3(@types/node@25.2.3)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.1): dependencies: esbuild: 0.25.9 @@ -30580,50 +30534,6 @@ snapshots: - debug - typescript - vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.13)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@26.1.0)(msw@2.12.10(@types/node@24.10.13)(typescript@5.9.3))(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.1): - dependencies: - '@types/chai': 5.2.2 - '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(msw@2.12.10(@types/node@24.10.13)(typescript@5.9.3))(vite@7.1.3(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.1)) - '@vitest/pretty-format': 3.2.4 - '@vitest/runner': 3.2.4 - '@vitest/snapshot': 3.2.4 - '@vitest/spy': 3.2.4 - '@vitest/utils': 3.2.4 - chai: 5.2.1 - debug: 4.4.1 - expect-type: 1.2.2 - magic-string: 0.30.17 - pathe: 2.0.3 - picomatch: 4.0.3 - std-env: 3.9.0 - tinybench: 2.9.0 - tinyexec: 0.3.2 - tinyglobby: 0.2.14 - tinypool: 1.1.1 - tinyrainbow: 2.0.0 - vite: 7.1.3(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.1) - vite-node: 3.2.4(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.1) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/debug': 4.1.12 - '@types/node': 24.10.13 - '@vitest/ui': 3.2.4(vitest@3.2.4) - jsdom: 26.1.0 - transitivePeerDependencies: - - jiti - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.2.3)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@26.1.0)(msw@2.12.10(@types/node@25.2.3)(typescript@5.9.3))(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.1): dependencies: '@types/chai': 5.2.2 @@ -30748,6 +30658,46 @@ snapshots: - tsx - yaml + vitest@4.0.15(@opentelemetry/api@1.9.0)(@types/node@24.10.13)(@vitest/ui@4.0.15)(jiti@2.6.1)(jsdom@26.1.0)(msw@2.12.10(@types/node@24.10.13)(typescript@5.9.3))(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.1): + dependencies: + '@vitest/expect': 4.0.15 + '@vitest/mocker': 4.0.15(msw@2.12.10(@types/node@24.10.13)(typescript@5.9.3))(vite@7.3.1(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.1)) + '@vitest/pretty-format': 4.0.15 + '@vitest/runner': 4.0.15 + '@vitest/snapshot': 4.0.15 + '@vitest/spy': 4.0.15 + '@vitest/utils': 4.0.15 + es-module-lexer: 1.7.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 1.0.2 + tinyglobby: 0.2.15 + tinyrainbow: 3.0.3 + vite: 7.3.1(@types/node@24.10.13)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@opentelemetry/api': 1.9.0 + '@types/node': 24.10.13 + '@vitest/ui': 4.0.15(vitest@4.0.15) + jsdom: 26.1.0 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - yaml + vitest@4.0.15(@opentelemetry/api@1.9.0)(@types/node@25.2.3)(@vitest/ui@4.0.15)(jiti@2.6.1)(jsdom@26.1.0)(msw@2.12.10(@types/node@25.2.3)(typescript@5.9.3))(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.1): dependencies: '@vitest/expect': 4.0.15