diff --git a/packages/node/package.json b/packages/node/package.json index 9a7091ef57fc..94792d431e18 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -118,9 +118,8 @@ "clean": "rimraf build coverage sentry-node-*.tgz", "fix": "eslint . --format stylish --fix", "lint": "eslint . --format stylish", - "test": "yarn test:jest", - "test:jest": "jest", - "test:watch": "jest --watch", + "test": "vitest run", + "test:watch": "vitest watch", "yalc:publish": "yalc publish --push --sig" }, "volta": { diff --git a/packages/node/test/cron.test.ts b/packages/node/test/cron.test.ts index d068280a41e0..466c6a67bc01 100644 --- a/packages/node/test/cron.test.ts +++ b/packages/node/test/cron.test.ts @@ -4,15 +4,18 @@ import { cron } from '../src'; import type { CronJob, CronJobParams } from '../src/cron/cron'; import type { NodeCron, NodeCronOptions } from '../src/cron/node-cron'; +import type { MockInstance } from 'vitest'; +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; + describe('cron check-ins', () => { - let withMonitorSpy: jest.SpyInstance; + let withMonitorSpy: MockInstance; beforeEach(() => { - withMonitorSpy = jest.spyOn(SentryCore, 'withMonitor'); + withMonitorSpy = vi.spyOn(SentryCore, 'withMonitor'); }); afterEach(() => { - jest.restoreAllMocks(); + vi.restoreAllMocks(); }); describe('cron', () => { @@ -48,43 +51,45 @@ describe('cron check-ins', () => { } } - test('new CronJob()', done => { - expect.assertions(4); - - const CronJobWithCheckIn = cron.instrumentCron(CronJobMock, 'my-cron-job'); - - new CronJobWithCheckIn( - '* * * Jan,Sep Sun', - () => { - expect(withMonitorSpy).toHaveBeenCalledTimes(1); - expect(withMonitorSpy).toHaveBeenLastCalledWith('my-cron-job', expect.anything(), { - schedule: { type: 'crontab', value: '* * * 1,9 0' }, - timezone: 'America/Los_Angeles', - }); - done(); - }, - undefined, - true, - 'America/Los_Angeles', - ); - }); + test('new CronJob()', () => + new Promise(done => { + expect.assertions(4); + + const CronJobWithCheckIn = cron.instrumentCron(CronJobMock, 'my-cron-job'); + + new CronJobWithCheckIn( + '* * * Jan,Sep Sun', + () => { + expect(withMonitorSpy).toHaveBeenCalledTimes(1); + expect(withMonitorSpy).toHaveBeenLastCalledWith('my-cron-job', expect.anything(), { + schedule: { type: 'crontab', value: '* * * 1,9 0' }, + timezone: 'America/Los_Angeles', + }); + done(); + }, + undefined, + true, + 'America/Los_Angeles', + ); + })); - test('CronJob.from()', done => { - expect.assertions(4); + test('CronJob.from()', () => + new Promise(done => { + expect.assertions(4); - const CronJobWithCheckIn = cron.instrumentCron(CronJobMock, 'my-cron-job'); + const CronJobWithCheckIn = cron.instrumentCron(CronJobMock, 'my-cron-job'); - CronJobWithCheckIn.from({ - cronTime: '* * * Jan,Sep Sun', - onTick: () => { - expect(withMonitorSpy).toHaveBeenCalledTimes(1); - expect(withMonitorSpy).toHaveBeenLastCalledWith('my-cron-job', expect.anything(), { - schedule: { type: 'crontab', value: '* * * 1,9 0' }, - }); - done(); - }, - }); - }); + CronJobWithCheckIn.from({ + cronTime: '* * * Jan,Sep Sun', + onTick: () => { + expect(withMonitorSpy).toHaveBeenCalledTimes(1); + expect(withMonitorSpy).toHaveBeenLastCalledWith('my-cron-job', expect.anything(), { + schedule: { type: 'crontab', value: '* * * 1,9 0' }, + }); + done(); + }, + }); + })); test('throws with multiple jobs same name', () => { const CronJobWithCheckIn = cron.instrumentCron(CronJobMock, 'my-cron-job'); @@ -108,32 +113,33 @@ describe('cron check-ins', () => { }); describe('node-cron', () => { - test('calls withMonitor', done => { - expect.assertions(5); - - const nodeCron: NodeCron = { - schedule: (expression: string, callback: () => void, options?: NodeCronOptions): unknown => { - expect(expression).toBe('* * * Jan,Sep Sun'); - expect(callback).toBeInstanceOf(Function); - expect(options?.name).toBe('my-cron-job'); - return callback(); - }, - }; - - const cronWithCheckIn = cron.instrumentNodeCron(nodeCron); - - cronWithCheckIn.schedule( - '* * * Jan,Sep Sun', - () => { - expect(withMonitorSpy).toHaveBeenCalledTimes(1); - expect(withMonitorSpy).toHaveBeenLastCalledWith('my-cron-job', expect.anything(), { - schedule: { type: 'crontab', value: '* * * 1,9 0' }, - }); - done(); - }, - { name: 'my-cron-job' }, - ); - }); + test('calls withMonitor', () => + new Promise(done => { + expect.assertions(5); + + const nodeCron: NodeCron = { + schedule: (expression: string, callback: () => void, options?: NodeCronOptions): unknown => { + expect(expression).toBe('* * * Jan,Sep Sun'); + expect(callback).toBeInstanceOf(Function); + expect(options?.name).toBe('my-cron-job'); + return callback(); + }, + }; + + const cronWithCheckIn = cron.instrumentNodeCron(nodeCron); + + cronWithCheckIn.schedule( + '* * * Jan,Sep Sun', + () => { + expect(withMonitorSpy).toHaveBeenCalledTimes(1); + expect(withMonitorSpy).toHaveBeenLastCalledWith('my-cron-job', expect.anything(), { + schedule: { type: 'crontab', value: '* * * 1,9 0' }, + }); + done(); + }, + { name: 'my-cron-job' }, + ); + })); test('throws without supplied name', () => { const nodeCron: NodeCron = { @@ -154,32 +160,33 @@ describe('cron check-ins', () => { }); describe('node-schedule', () => { - test('calls withMonitor', done => { - expect.assertions(5); - - class NodeScheduleMock { - scheduleJob( - nameOrExpression: string | Date | object, - expressionOrCallback: string | Date | object | (() => void), - callback: () => void, - ): unknown { - expect(nameOrExpression).toBe('my-cron-job'); - expect(expressionOrCallback).toBe('* * * Jan,Sep Sun'); - expect(callback).toBeInstanceOf(Function); - return callback(); + test('calls withMonitor', () => + new Promise(done => { + expect.assertions(5); + + class NodeScheduleMock { + scheduleJob( + nameOrExpression: string | Date | object, + expressionOrCallback: string | Date | object | (() => void), + callback: () => void, + ): unknown { + expect(nameOrExpression).toBe('my-cron-job'); + expect(expressionOrCallback).toBe('* * * Jan,Sep Sun'); + expect(callback).toBeInstanceOf(Function); + return callback(); + } } - } - const scheduleWithCheckIn = cron.instrumentNodeSchedule(new NodeScheduleMock()); + const scheduleWithCheckIn = cron.instrumentNodeSchedule(new NodeScheduleMock()); - scheduleWithCheckIn.scheduleJob('my-cron-job', '* * * Jan,Sep Sun', () => { - expect(withMonitorSpy).toHaveBeenCalledTimes(1); - expect(withMonitorSpy).toHaveBeenLastCalledWith('my-cron-job', expect.anything(), { - schedule: { type: 'crontab', value: '* * * 1,9 0' }, + scheduleWithCheckIn.scheduleJob('my-cron-job', '* * * Jan,Sep Sun', () => { + expect(withMonitorSpy).toHaveBeenCalledTimes(1); + expect(withMonitorSpy).toHaveBeenLastCalledWith('my-cron-job', expect.anything(), { + schedule: { type: 'crontab', value: '* * * 1,9 0' }, + }); + done(); }); - done(); - }); - }); + })); test('throws without crontab string', () => { class NodeScheduleMock { diff --git a/packages/node/test/integration/breadcrumbs.test.ts b/packages/node/test/integration/breadcrumbs.test.ts index 5105a9ff50df..482d2948abcd 100644 --- a/packages/node/test/integration/breadcrumbs.test.ts +++ b/packages/node/test/integration/breadcrumbs.test.ts @@ -5,17 +5,19 @@ import type { NodeClient } from '../../src/sdk/client'; import { cleanupOtel, mockSdkInit } from '../helpers/mockSdkInit'; +import { afterEach, describe, expect, test, vi } from 'vitest'; + describe('Integration | breadcrumbs', () => { - const beforeSendTransaction = jest.fn(() => null); + const beforeSendTransaction = vi.fn(() => null); afterEach(() => { cleanupOtel(); }); describe('without tracing', () => { - it('correctly adds & retrieves breadcrumbs', async () => { - const beforeSend = jest.fn(() => null); - const beforeBreadcrumb = jest.fn(breadcrumb => breadcrumb); + test('correctly adds & retrieves breadcrumbs', async () => { + const beforeSend = vi.fn(() => null); + const beforeBreadcrumb = vi.fn(breadcrumb => breadcrumb); mockSdkInit({ beforeSend, beforeBreadcrumb }); @@ -49,9 +51,9 @@ describe('Integration | breadcrumbs', () => { ); }); - it('handles parallel scopes', async () => { - const beforeSend = jest.fn(() => null); - const beforeBreadcrumb = jest.fn(breadcrumb => breadcrumb); + test('handles parallel scopes', async () => { + const beforeSend = vi.fn(() => null); + const beforeBreadcrumb = vi.fn(breadcrumb => breadcrumb); mockSdkInit({ beforeSend, beforeBreadcrumb }); @@ -95,9 +97,9 @@ describe('Integration | breadcrumbs', () => { }); }); - it('correctly adds & retrieves breadcrumbs', async () => { - const beforeSend = jest.fn(() => null); - const beforeBreadcrumb = jest.fn(breadcrumb => breadcrumb); + test('correctly adds & retrieves breadcrumbs', async () => { + const beforeSend = vi.fn(() => null); + const beforeBreadcrumb = vi.fn(breadcrumb => breadcrumb); mockSdkInit({ beforeSend, beforeBreadcrumb, beforeSendTransaction, enableTracing: true }); @@ -140,9 +142,9 @@ describe('Integration | breadcrumbs', () => { ); }); - it('correctly adds & retrieves breadcrumbs for the current isolation span only', async () => { - const beforeSend = jest.fn(() => null); - const beforeBreadcrumb = jest.fn(breadcrumb => breadcrumb); + test('correctly adds & retrieves breadcrumbs for the current isolation span only', async () => { + const beforeSend = vi.fn(() => null); + const beforeBreadcrumb = vi.fn(breadcrumb => breadcrumb); mockSdkInit({ beforeSend, beforeBreadcrumb, beforeSendTransaction, enableTracing: true }); @@ -192,9 +194,9 @@ describe('Integration | breadcrumbs', () => { ); }); - it('ignores scopes inside of root span', async () => { - const beforeSend = jest.fn(() => null); - const beforeBreadcrumb = jest.fn(breadcrumb => breadcrumb); + test('ignores scopes inside of root span', async () => { + const beforeSend = vi.fn(() => null); + const beforeBreadcrumb = vi.fn(breadcrumb => breadcrumb); mockSdkInit({ beforeSend, beforeBreadcrumb, beforeSendTransaction, enableTracing: true }); @@ -233,9 +235,9 @@ describe('Integration | breadcrumbs', () => { ); }); - it('handles deep nesting of scopes', async () => { - const beforeSend = jest.fn(() => null); - const beforeBreadcrumb = jest.fn(breadcrumb => breadcrumb); + test('handles deep nesting of scopes', async () => { + const beforeSend = vi.fn(() => null); + const beforeBreadcrumb = vi.fn(breadcrumb => breadcrumb); mockSdkInit({ beforeSend, beforeBreadcrumb, beforeSendTransaction, enableTracing: true }); @@ -291,9 +293,9 @@ describe('Integration | breadcrumbs', () => { ); }); - it('correctly adds & retrieves breadcrumbs in async spans', async () => { - const beforeSend = jest.fn(() => null); - const beforeBreadcrumb = jest.fn(breadcrumb => breadcrumb); + test('correctly adds & retrieves breadcrumbs in async spans', async () => { + const beforeSend = vi.fn(() => null); + const beforeBreadcrumb = vi.fn(breadcrumb => breadcrumb); mockSdkInit({ beforeSend, beforeBreadcrumb, beforeSendTransaction, enableTracing: true }); diff --git a/packages/node/test/integration/scope.test.ts b/packages/node/test/integration/scope.test.ts index 1a7a899ab423..a502f15a5387 100644 --- a/packages/node/test/integration/scope.test.ts +++ b/packages/node/test/integration/scope.test.ts @@ -6,6 +6,8 @@ import * as Sentry from '../../src/'; import type { NodeClient } from '../../src/sdk/client'; import { cleanupOtel, mockSdkInit, resetGlobals } from '../helpers/mockSdkInit'; +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; + describe('Integration | Scope', () => { afterEach(() => { cleanupOtel(); @@ -15,9 +17,9 @@ describe('Integration | Scope', () => { ['with tracing', true], ['without tracing', false], ])('%s', (_name, enableTracing) => { - it('correctly syncs OTEL context & Sentry hub/scope', async () => { - const beforeSend = jest.fn(() => null); - const beforeSendTransaction = jest.fn(() => null); + test('correctly syncs OTEL context & Sentry hub/scope', async () => { + const beforeSend = vi.fn(() => null); + const beforeSendTransaction = vi.fn(() => null); mockSdkInit({ enableTracing, beforeSend, beforeSendTransaction }); @@ -129,9 +131,9 @@ describe('Integration | Scope', () => { } }); - it('isolates parallel root scopes', async () => { - const beforeSend = jest.fn(() => null); - const beforeSendTransaction = jest.fn(() => null); + test('isolates parallel root scopes', async () => { + const beforeSend = vi.fn(() => null); + const beforeSendTransaction = vi.fn(() => null); mockSdkInit({ enableTracing, beforeSend, beforeSendTransaction }); @@ -243,7 +245,7 @@ describe('Integration | Scope', () => { clearGlobalScope(); }); - it('works before calling init', () => { + test('works before calling init', () => { const globalScope = Sentry.getGlobalScope(); expect(globalScope).toBeDefined(); // No client attached @@ -264,8 +266,8 @@ describe('Integration | Scope', () => { expect(globalScope.getScopeData().tags).toEqual({ tag1: 'val1', tag2: 'val2' }); }); - it('is applied to events', async () => { - const beforeSend = jest.fn(); + test('is applied to events', async () => { + const beforeSend = vi.fn(); mockSdkInit({ beforeSend }); const client = Sentry.getClient(); @@ -300,7 +302,7 @@ describe('Integration | Scope', () => { resetGlobals(); }); - it('works before calling init', () => { + test('works before calling init', () => { const isolationScope = Sentry.getIsolationScope(); expect(isolationScope).toBeDefined(); // No client attached @@ -322,8 +324,8 @@ describe('Integration | Scope', () => { expect(isolationScope.getScopeData().tags).toEqual({ tag1: 'val1', tag2: 'val2' }); }); - it('is applied to events', async () => { - const beforeSend = jest.fn(); + test('is applied to events', async () => { + const beforeSend = vi.fn(); mockSdkInit({ beforeSend }); const client = Sentry.getClient(); @@ -352,8 +354,8 @@ describe('Integration | Scope', () => { ); }); - it('withIsolationScope works', async () => { - const beforeSend = jest.fn(); + test('withIsolationScope works', async () => { + const beforeSend = vi.fn(); mockSdkInit({ beforeSend }); const client = Sentry.getClient(); @@ -402,8 +404,8 @@ describe('Integration | Scope', () => { ); }); - it('can be deeply nested', async () => { - const beforeSend = jest.fn(); + test('can be deeply nested', async () => { + const beforeSend = vi.fn(); mockSdkInit({ beforeSend }); const client = Sentry.getClient(); @@ -451,7 +453,7 @@ describe('Integration | Scope', () => { resetGlobals(); }); - it('works before calling init', () => { + test('works before calling init', () => { const currentScope = Sentry.getCurrentScope(); expect(currentScope).toBeDefined(); // No client attached @@ -474,8 +476,8 @@ describe('Integration | Scope', () => { expect(currentScope.getScopeData().tags).toEqual({ tag1: 'val1', tag2: 'val2' }); }); - it('is applied to events', async () => { - const beforeSend = jest.fn(); + test('is applied to events', async () => { + const beforeSend = vi.fn(); mockSdkInit({ beforeSend }); const client = Sentry.getClient(); @@ -504,8 +506,8 @@ describe('Integration | Scope', () => { ); }); - it('withScope works', async () => { - const beforeSend = jest.fn(); + test('withScope works', async () => { + const beforeSend = vi.fn(); mockSdkInit({ beforeSend }); const client = Sentry.getClient(); @@ -553,8 +555,8 @@ describe('Integration | Scope', () => { ); }); - it('can be deeply nested', async () => { - const beforeSend = jest.fn(); + test('can be deeply nested', async () => { + const beforeSend = vi.fn(); mockSdkInit({ beforeSend }); const client = Sentry.getClient(); @@ -599,8 +601,8 @@ describe('Integration | Scope', () => { ); }); - it('automatically forks with OTEL context', async () => { - const beforeSend = jest.fn(); + test('automatically forks with OTEL context', async () => { + const beforeSend = vi.fn(); mockSdkInit({ beforeSend }); const client = Sentry.getClient(); @@ -648,8 +650,8 @@ describe('Integration | Scope', () => { resetGlobals(); }); - it('merges data from global, isolation and current scope', async () => { - const beforeSend = jest.fn(); + test('merges data from global, isolation and current scope', async () => { + const beforeSend = vi.fn(); mockSdkInit({ beforeSend }); const client = Sentry.getClient(); diff --git a/packages/node/test/integration/transactions.test.ts b/packages/node/test/integration/transactions.test.ts index 048496f363b4..512f253bd175 100644 --- a/packages/node/test/integration/transactions.test.ts +++ b/packages/node/test/integration/transactions.test.ts @@ -8,15 +8,17 @@ import { logger } from '@sentry/utils'; import * as Sentry from '../../src'; import { cleanupOtel, getProvider, mockSdkInit } from '../helpers/mockSdkInit'; +import { afterEach, describe, expect, test, vi } from 'vitest'; + describe('Integration | Transactions', () => { afterEach(() => { - jest.restoreAllMocks(); + vi.restoreAllMocks(); cleanupOtel(); }); - it('correctly creates transaction & spans', async () => { + test('correctly creates transaction & spans', async () => { const transactions: TransactionEvent[] = []; - const beforeSendTransaction = jest.fn(event => { + const beforeSendTransaction = vi.fn(event => { transactions.push(event); return null; }); @@ -160,8 +162,8 @@ describe('Integration | Transactions', () => { ]); }); - it('correctly creates concurrent transaction & spans', async () => { - const beforeSendTransaction = jest.fn(() => null); + test('correctly creates concurrent transaction & spans', async () => { + const beforeSendTransaction = vi.fn(() => null); mockSdkInit({ enableTracing: true, beforeSendTransaction }); @@ -305,8 +307,8 @@ describe('Integration | Transactions', () => { ); }); - it('correctly creates concurrent transaction & spans when using native OTEL tracer', async () => { - const beforeSendTransaction = jest.fn(() => null); + test('correctly creates concurrent transaction & spans when using native OTEL tracer', async () => { + const beforeSendTransaction = vi.fn(() => null); mockSdkInit({ enableTracing: true, beforeSendTransaction }); @@ -442,8 +444,8 @@ describe('Integration | Transactions', () => { ); }); - it('correctly creates transaction & spans with a trace header data', async () => { - const beforeSendTransaction = jest.fn(() => null); + test('correctly creates transaction & spans with a trace header data', async () => { + const beforeSendTransaction = vi.fn(() => null); const traceId = 'd4cda95b652f4a1592b449d5929fda1b'; const parentSpanId = '6e0c63257de34c92'; @@ -551,15 +553,15 @@ describe('Integration | Transactions', () => { ]); }); - it('cleans up spans that are not flushed for over 5 mins', async () => { - const beforeSendTransaction = jest.fn(() => null); + test('cleans up spans that are not flushed for over 5 mins', async () => { + const beforeSendTransaction = vi.fn(() => null); const now = Date.now(); - jest.useFakeTimers(); - jest.setSystemTime(now); + vi.useFakeTimers(); + vi.setSystemTime(now); const logs: unknown[] = []; - jest.spyOn(logger, 'log').mockImplementation(msg => logs.push(msg)); + vi.spyOn(logger, 'log').mockImplementation(msg => logs.push(msg)); mockSdkInit({ enableTracing: true, beforeSendTransaction }); @@ -593,19 +595,19 @@ describe('Integration | Transactions', () => { await new Promise(resolve => setTimeout(resolve, 10 * 60 * 1000)); }); - jest.advanceTimersByTime(1); + vi.advanceTimersByTime(1); // Child-spans have been added to the exporter, but they are pending since they are waiting for their parant expect(exporter['_finishedSpans'].length).toBe(2); expect(beforeSendTransaction).toHaveBeenCalledTimes(0); // Now wait for 5 mins - jest.advanceTimersByTime(5 * 60 * 1_000 + 1); + vi.advanceTimersByTime(5 * 60 * 1_000 + 1); // Adding another span will trigger the cleanup Sentry.startSpan({ name: 'other span' }, () => {}); - jest.advanceTimersByTime(1); + vi.advanceTimersByTime(1); // Old spans have been cleared away expect(exporter['_finishedSpans'].length).toBe(0); @@ -624,19 +626,19 @@ describe('Integration | Transactions', () => { ); }); - it('allows to configure `maxSpanWaitDuration` to capture long running spans', async () => { + test('allows to configure `maxSpanWaitDuration` to capture long running spans', async () => { const transactions: TransactionEvent[] = []; - const beforeSendTransaction = jest.fn(event => { + const beforeSendTransaction = vi.fn(event => { transactions.push(event); return null; }); const now = Date.now(); - jest.useFakeTimers(); - jest.setSystemTime(now); + vi.useFakeTimers(); + vi.setSystemTime(now); const logs: unknown[] = []; - jest.spyOn(logger, 'log').mockImplementation(msg => logs.push(msg)); + vi.spyOn(logger, 'log').mockImplementation(msg => logs.push(msg)); mockSdkInit({ enableTracing: true, @@ -668,7 +670,7 @@ describe('Integration | Transactions', () => { }); // Now wait for 100 mins - jest.advanceTimersByTime(100 * 60 * 1_000); + vi.advanceTimersByTime(100 * 60 * 1_000); expect(beforeSendTransaction).toHaveBeenCalledTimes(1); expect(transactions).toHaveLength(1); diff --git a/packages/node/test/integrations/context.test.ts b/packages/node/test/integrations/context.test.ts index dde182c552ba..7ba4ea23f800 100644 --- a/packages/node/test/integrations/context.test.ts +++ b/packages/node/test/integrations/context.test.ts @@ -3,10 +3,16 @@ import * as os from 'node:os'; import { getAppContext, getDeviceContext } from '../../src/integrations/context'; import { conditionalTest } from '../helpers/conditional'; +import { afterAll, describe, expect, test, vi } from 'vitest'; + +vi.mock('node:os', async () => { + return { __esModule: true, ...(await import('node:os')) }; +}); + describe('Context', () => { describe('getAppContext', () => { afterAll(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); conditionalTest({ max: 18 })('it does not return free_memory on older node versions', () => { @@ -23,7 +29,7 @@ describe('Context', () => { ); conditionalTest({ min: 22 })('returns no free_memory if process.availableMemory ', () => { - jest.spyOn(process as any, 'availableMemory').mockReturnValue(undefined as unknown as number); + vi.spyOn(process as any, 'availableMemory').mockReturnValue(undefined as unknown as number); const appContext = getAppContext(); expect(appContext.free_memory).toBeUndefined(); }); @@ -31,16 +37,16 @@ describe('Context', () => { describe('getDeviceContext', () => { afterAll(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); - it('returns boot time if os.uptime is defined and returns a valid uptime', () => { + test('returns boot time if os.uptime is defined and returns a valid uptime', () => { const deviceCtx = getDeviceContext({}); expect(deviceCtx.boot_time).toEqual(expect.any(String)); }); - it('returns no boot time if os.uptime() returns undefined', () => { - jest.spyOn(os, 'uptime').mockReturnValue(undefined as unknown as number); + test('returns no boot time if os.uptime() returns undefined', () => { + vi.spyOn(os, 'uptime').mockReturnValue(undefined as unknown as number); const deviceCtx = getDeviceContext({}); expect(deviceCtx.boot_time).toBeUndefined(); }); diff --git a/packages/node/test/integrations/contextlines.test.ts b/packages/node/test/integrations/contextlines.test.ts index 56af4a3e58e8..519e9a3342f4 100644 --- a/packages/node/test/integrations/contextlines.test.ts +++ b/packages/node/test/integrations/contextlines.test.ts @@ -11,6 +11,12 @@ import { import { defaultStackParser } from '../../src/sdk/api'; import { getError } from '../helpers/error'; +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; + +vi.mock('node:fs', async () => { + return { __esModule: true, ...(await import('node:fs')) }; +}); + describe('ContextLines', () => { let contextLines: ReturnType; @@ -24,7 +30,7 @@ describe('ContextLines', () => { }); afterEach(() => { - jest.clearAllMocks(); + vi.clearAllMocks(); }); describe('limits', () => { @@ -39,7 +45,7 @@ describe('ContextLines', () => { }, ]; - const readStreamSpy = jest.spyOn(fs, 'createReadStream'); + const readStreamSpy = vi.spyOn(fs, 'createReadStream'); await addContext(frames); expect(readStreamSpy).not.toHaveBeenCalled(); }); @@ -55,7 +61,7 @@ describe('ContextLines', () => { }, ]; - const readStreamSpy = jest.spyOn(fs, 'createReadStream'); + const readStreamSpy = vi.spyOn(fs, 'createReadStream'); await addContext(frames); expect(readStreamSpy).not.toHaveBeenCalled(); }); @@ -73,7 +79,7 @@ describe('ContextLines', () => { }, ]; - const readStreamSpy = jest.spyOn(fs, 'createReadStream'); + const readStreamSpy = vi.spyOn(fs, 'createReadStream'); await addContext(frames); expect(frames[0]!.pre_context).toBeUndefined(); @@ -85,7 +91,7 @@ describe('ContextLines', () => { expect.assertions(1); const frames = parseStackFrames(defaultStackParser, new Error('test')); - const readStreamSpy = jest.spyOn(fs, 'createReadStream'); + const readStreamSpy = vi.spyOn(fs, 'createReadStream'); await addContext(frames); const numCalls = readStreamSpy.mock.calls.length; @@ -99,7 +105,7 @@ describe('ContextLines', () => { test('parseStack with ESM module names', async () => { expect.assertions(1); - const readStreamSpy = jest.spyOn(fs, 'createReadStream'); + const readStreamSpy = vi.spyOn(fs, 'createReadStream'); const framesWithFilePath: StackFrame[] = [ { colno: 1, @@ -116,7 +122,7 @@ describe('ContextLines', () => { test('parseStack with adding different file', async () => { expect.assertions(1); const frames = parseStackFrames(defaultStackParser, new Error('test')); - const readStreamSpy = jest.spyOn(fs, 'createReadStream'); + const readStreamSpy = vi.spyOn(fs, 'createReadStream'); await addContext(frames); @@ -170,7 +176,7 @@ describe('ContextLines', () => { test('parseStack with duplicate files', async () => { expect.assertions(1); - const readStreamSpy = jest.spyOn(fs, 'createReadStream'); + const readStreamSpy = vi.spyOn(fs, 'createReadStream'); const framesWithDuplicateFiles: StackFrame[] = [ { colno: 1, @@ -198,7 +204,7 @@ describe('ContextLines', () => { test('stack errors without lineno', async () => { expect.assertions(1); - const readStreamSpy = jest.spyOn(fs, 'createReadStream'); + const readStreamSpy = vi.spyOn(fs, 'createReadStream'); const framesWithDuplicateFiles: StackFrame[] = [ { colno: 1, @@ -215,7 +221,7 @@ describe('ContextLines', () => { test('parseStack with no context', async () => { expect.assertions(1); contextLines = _contextLinesIntegration({ frameContextLines: 0 }); - const readStreamSpy = jest.spyOn(fs, 'createReadStream'); + const readStreamSpy = vi.spyOn(fs, 'createReadStream'); const frames = parseStackFrames(defaultStackParser, new Error('test')); diff --git a/packages/node/test/integrations/express.test.ts b/packages/node/test/integrations/express.test.ts index 3071dce968d5..05962886385c 100644 --- a/packages/node/test/integrations/express.test.ts +++ b/packages/node/test/integrations/express.test.ts @@ -5,6 +5,8 @@ import { expressErrorHandler } from '../../src/integrations/tracing/express'; import { NodeClient } from '../../src/sdk/client'; import { getDefaultNodeClientOptions } from '../helpers/getDefaultNodeClientOptions'; +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; + describe('expressErrorHandler()', () => { beforeEach(() => { getCurrentScope().clear(); @@ -27,7 +29,7 @@ describe('expressErrorHandler()', () => { function createNoOpSpy() { const noop = { noop: () => undefined }; // this is wrapped in an object so jest can spy on it - return jest.spyOn(noop, 'noop') as any; + return vi.spyOn(noop, 'noop') as any; } beforeEach(() => { @@ -46,55 +48,57 @@ describe('expressErrorHandler()', () => { if (client['_sessionFlusher']) { clearInterval(client['_sessionFlusher']['_intervalId']); } - jest.restoreAllMocks(); + vi.restoreAllMocks(); }); - it('when autoSessionTracking is disabled, does not set requestSession status on Crash', done => { - const options = getDefaultNodeClientOptions({ autoSessionTracking: false, release: '3.3' }); - client = new NodeClient(options); - // It is required to initialise SessionFlusher to capture Session Aggregates (it is usually initialised - // by the`requestHandler`) - client.initSessionFlusher(); + test('when autoSessionTracking is disabled, does not set requestSession status on Crash', () => + new Promise(done => { + const options = getDefaultNodeClientOptions({ autoSessionTracking: false, release: '3.3' }); + client = new NodeClient(options); + // It is required to initialise SessionFlusher to capture Session Aggregates (it is usually initialised + // by the`requestHandler`) + client.initSessionFlusher(); - setCurrentClient(client); + setCurrentClient(client); - jest.spyOn(client, '_captureRequestSession'); + vi.spyOn(client, '_captureRequestSession'); - getIsolationScope().setRequestSession({ status: 'ok' }); + getIsolationScope().setRequestSession({ status: 'ok' }); - let isolationScope: Scope; - sentryErrorMiddleware({ name: 'error', message: 'this is an error' }, req, res, () => { - isolationScope = getIsolationScope(); - return next(); - }); + let isolationScope: Scope; + sentryErrorMiddleware({ name: 'error', message: 'this is an error' }, req, res, () => { + isolationScope = getIsolationScope(); + return next(); + }); - setImmediate(() => { - expect(isolationScope.getRequestSession()).toEqual({ status: 'ok' }); - done(); - }); - }); + setImmediate(() => { + expect(isolationScope.getRequestSession()).toEqual({ status: 'ok' }); + done(); + }); + })); - it('autoSessionTracking is enabled + requestHandler is not used -> does not set requestSession status on Crash', done => { - const options = getDefaultNodeClientOptions({ autoSessionTracking: true, release: '3.3' }); - client = new NodeClient(options); - setCurrentClient(client); + test('autoSessionTracking is enabled + requestHandler is not used -> does not set requestSession status on Crash', () => + new Promise(done => { + const options = getDefaultNodeClientOptions({ autoSessionTracking: true, release: '3.3' }); + client = new NodeClient(options); + setCurrentClient(client); - jest.spyOn(client, '_captureRequestSession'); + vi.spyOn(client, '_captureRequestSession'); - getIsolationScope().setRequestSession({ status: 'ok' }); + getIsolationScope().setRequestSession({ status: 'ok' }); - let isolationScope: Scope; - sentryErrorMiddleware({ name: 'error', message: 'this is an error' }, req, res, () => { - isolationScope = getIsolationScope(); - return next(); - }); + let isolationScope: Scope; + sentryErrorMiddleware({ name: 'error', message: 'this is an error' }, req, res, () => { + isolationScope = getIsolationScope(); + return next(); + }); - setImmediate(() => { - expect(isolationScope.getRequestSession()).toEqual({ status: 'ok' }); - done(); - }); - }); + setImmediate(() => { + expect(isolationScope.getRequestSession()).toEqual({ status: 'ok' }); + done(); + }); + })); - it('when autoSessionTracking is enabled, should set requestSession status to Crashed when an unhandled error occurs within the bounds of a request', () => { + test('when autoSessionTracking is enabled, should set requestSession status to Crashed when an unhandled error occurs within the bounds of a request', () => { const options = getDefaultNodeClientOptions({ autoSessionTracking: true, release: '1.1' }); client = new NodeClient(options); // It is required to initialise SessionFlusher to capture Session Aggregates (it is usually initialised @@ -103,7 +107,7 @@ describe('expressErrorHandler()', () => { setCurrentClient(client); - jest.spyOn(client, '_captureRequestSession'); + vi.spyOn(client, '_captureRequestSession'); withScope(() => { getIsolationScope().setRequestSession({ status: 'ok' }); @@ -113,25 +117,26 @@ describe('expressErrorHandler()', () => { }); }); - it('when autoSessionTracking is enabled, should not set requestSession status on Crash when it occurs outside the bounds of a request', done => { - const options = getDefaultNodeClientOptions({ autoSessionTracking: true, release: '2.2' }); - client = new NodeClient(options); - // It is required to initialise SessionFlusher to capture Session Aggregates (it is usually initialised - // by the`requestHandler`) - client.initSessionFlusher(); - setCurrentClient(client); + test('when autoSessionTracking is enabled, should not set requestSession status on Crash when it occurs outside the bounds of a request', () => + new Promise(done => { + const options = getDefaultNodeClientOptions({ autoSessionTracking: true, release: '2.2' }); + client = new NodeClient(options); + // It is required to initialise SessionFlusher to capture Session Aggregates (it is usually initialised + // by the`requestHandler`) + client.initSessionFlusher(); + setCurrentClient(client); - jest.spyOn(client, '_captureRequestSession'); + vi.spyOn(client, '_captureRequestSession'); - let isolationScope: Scope; - sentryErrorMiddleware({ name: 'error', message: 'this is an error' }, req, res, () => { - isolationScope = getIsolationScope(); - return next(); - }); + let isolationScope: Scope; + sentryErrorMiddleware({ name: 'error', message: 'this is an error' }, req, res, () => { + isolationScope = getIsolationScope(); + return next(); + }); - setImmediate(() => { - expect(isolationScope.getRequestSession()).toEqual(undefined); - done(); - }); - }); + setImmediate(() => { + expect(isolationScope.getRequestSession()).toEqual(undefined); + done(); + }); + })); }); diff --git a/packages/node/test/integrations/localvariables.test.ts b/packages/node/test/integrations/localvariables.test.ts index db9385214d42..8eb8537536ad 100644 --- a/packages/node/test/integrations/localvariables.test.ts +++ b/packages/node/test/integrations/localvariables.test.ts @@ -2,95 +2,110 @@ import { createRateLimiter } from '../../src/integrations/local-variables/common import { createCallbackList } from '../../src/integrations/local-variables/local-variables-sync'; import { NODE_MAJOR } from '../../src/nodeVersion'; -jest.setTimeout(20_000); +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; + +vi.setConfig({ testTimeout: 20_000 }); const describeIf = (condition: boolean) => (condition ? describe : describe.skip); describeIf(NODE_MAJOR >= 18)('LocalVariables', () => { describe('createCallbackList', () => { - it('Should call callbacks in reverse order', done => { - const log: number[] = []; - - const { add, next } = createCallbackList(n => { - expect(log).toEqual([5, 4, 3, 2, 1]); - expect(n).toBe(15); - done(); - }); - - add(n => { - log.push(1); - next(n + 1); - }); - - add(n => { - log.push(2); - next(n + 1); - }); - - add(n => { - log.push(3); - next(n + 1); - }); - - add(n => { - log.push(4); - next(n + 1); - }); - - add(n => { - log.push(5); - next(n + 11); - }); - - next(0); - }); + test('Should call callbacks in reverse order', () => + new Promise(done => { + const log: number[] = []; - it('only calls complete once even if multiple next', done => { - const { add, next } = createCallbackList(n => { - expect(n).toBe(1); - done(); - }); + const { add, next } = createCallbackList(n => { + expect(log).toEqual([5, 4, 3, 2, 1]); + expect(n).toBe(15); + done(); + }); + + add(n => { + log.push(1); + next(n + 1); + }); + + add(n => { + log.push(2); + next(n + 1); + }); + + add(n => { + log.push(3); + next(n + 1); + }); + + add(n => { + log.push(4); + next(n + 1); + }); + + add(n => { + log.push(5); + next(n + 11); + }); + + next(0); + })); + + test('only calls complete once even if multiple next', () => + new Promise(done => { + const { add, next } = createCallbackList(n => { + expect(n).toBe(1); + done(); + }); - add(n => { - next(n + 1); - // We dont actually do this in our code... - next(n + 1); - }); + add(n => { + next(n + 1); + // We dont actually do this in our code... + next(n + 1); + }); - next(0); - }); + next(0); + })); - it('calls completed if added closure throws', done => { - const { add, next } = createCallbackList(n => { - expect(n).toBe(10); - done(); - }); + test('calls completed if added closure throws', () => + new Promise(done => { + const { add, next } = createCallbackList(n => { + expect(n).toBe(10); + done(); + }); - add(n => { - throw new Error('test'); - next(n + 1); - }); + add(n => { + throw new Error('test'); + next(n + 1); + }); - next(10); - }); + next(10); + })); }); describe('rateLimiter', () => { - it('calls disable if exceeded', done => { - const increment = createRateLimiter( - 5, - () => {}, - () => { - done(); - }, - ); - - for (let i = 0; i < 7; i++) { - increment(); - } + beforeEach(() => { + vi.useFakeTimers(); }); + afterEach(() => { + vi.restoreAllMocks(); + }); + + test('calls disable if exceeded', () => + new Promise(done => { + const increment = createRateLimiter( + 5, + () => {}, + () => { + done(); + }, + ); + + for (let i = 0; i < 7; i++) { + increment(); + } - it('does not call disable if not exceeded', done => { + vi.runAllTimers(); + })); + + test('does not call disable if not exceeded', () => { const increment = createRateLimiter( 5, () => { @@ -101,30 +116,26 @@ describeIf(NODE_MAJOR >= 18)('LocalVariables', () => { }, ); - let count = 0; - - const timer = setInterval(() => { - for (let i = 0; i < 4; i++) { - increment(); - } + for (let i = 0; i < 4; i++) { + increment(); + vi.advanceTimersByTime(300); + } - count += 1; + vi.advanceTimersByTime(300); - if (count >= 5) { - clearInterval(timer); - done(); - } - }, 1_000); + for (let i = 0; i < 4; i++) { + increment(); + vi.advanceTimersByTime(300); + } }); - it('re-enables after timeout', done => { + test('re-enables after timeout', () => { let called = false; const increment = createRateLimiter( 5, () => { expect(called).toEqual(true); - done(); }, () => { expect(called).toEqual(false); @@ -134,6 +145,7 @@ describeIf(NODE_MAJOR >= 18)('LocalVariables', () => { for (let i = 0; i < 10; i++) { increment(); + vi.advanceTimersByTime(200); } }); }); diff --git a/packages/node/test/integrations/spotlight.test.ts b/packages/node/test/integrations/spotlight.test.ts index 6b888c22edcd..409eee678361 100644 --- a/packages/node/test/integrations/spotlight.test.ts +++ b/packages/node/test/integrations/spotlight.test.ts @@ -6,26 +6,32 @@ import { spotlightIntegration } from '../../src/integrations/spotlight'; import { NodeClient } from '../../src/sdk/client'; import { getDefaultNodeClientOptions } from '../helpers/getDefaultNodeClientOptions'; +import { afterEach, describe, expect, test, vi } from 'vitest'; + +vi.mock('node:http', async () => { + return { __esModule: true, ...(await import('node:http')) }; +}); + describe('Spotlight', () => { - const loggerSpy = jest.spyOn(logger, 'warn'); + const loggerSpy = vi.spyOn(logger, 'warn'); afterEach(() => { loggerSpy.mockClear(); - jest.clearAllMocks(); + vi.clearAllMocks(); }); const options = getDefaultNodeClientOptions(); const client = new NodeClient(options); - it('has a name', () => { + test('has a name', () => { const integration = spotlightIntegration(); expect(integration.name).toEqual('Spotlight'); }); - it('registers a callback on the `beforeEnvelope` hook', () => { + test('registers a callback on the `beforeEnvelope` hook', () => { const clientWithSpy = { ...client, - on: jest.fn(), + on: vi.fn(), }; const integration = spotlightIntegration(); // @ts-expect-error - this is fine in tests @@ -33,19 +39,19 @@ describe('Spotlight', () => { expect(clientWithSpy.on).toHaveBeenCalledWith('beforeEnvelope', expect.any(Function)); }); - it('sends an envelope POST request to the sidecar url', () => { - const httpSpy = jest.spyOn(http, 'request').mockImplementationOnce(() => { + test('sends an envelope POST request to the sidecar url', () => { + const httpSpy = vi.spyOn(http, 'request').mockImplementationOnce(() => { return { - on: jest.fn(), - write: jest.fn(), - end: jest.fn(), + on: vi.fn(), + write: vi.fn(), + end: vi.fn(), } as any; }); let callback: (envelope: Envelope) => void = () => {}; const clientWithSpy = { ...client, - on: jest.fn().mockImplementationOnce((_, cb) => (callback = cb)), + on: vi.fn().mockImplementationOnce((_, cb) => (callback = cb)), }; const integration = spotlightIntegration(); @@ -72,19 +78,19 @@ describe('Spotlight', () => { ); }); - it('sends an envelope POST request to a custom sidecar url', () => { - const httpSpy = jest.spyOn(http, 'request').mockImplementationOnce(() => { + test('sends an envelope POST request to a custom sidecar url', () => { + const httpSpy = vi.spyOn(http, 'request').mockImplementationOnce(() => { return { - on: jest.fn(), - write: jest.fn(), - end: jest.fn(), + on: vi.fn(), + write: vi.fn(), + end: vi.fn(), } as any; }); let callback: (envelope: Envelope) => void = () => {}; const clientWithSpy = { ...client, - on: jest.fn().mockImplementationOnce((_, cb) => (callback = cb)), + on: vi.fn().mockImplementationOnce((_, cb) => (callback = cb)), }; const integration = spotlightIntegration({ sidecarUrl: 'http://mylocalhost:8888/abcd' }); @@ -112,14 +118,14 @@ describe('Spotlight', () => { }); describe('no-ops if', () => { - it('an invalid URL is passed', () => { + test('an invalid URL is passed', () => { const integration = spotlightIntegration({ sidecarUrl: 'invalid-url' }); integration.setup!(client); expect(loggerSpy).toHaveBeenCalledWith(expect.stringContaining('Invalid sidecar URL: invalid-url')); }); }); - it('warns if the NODE_ENV variable doesn\'t equal "development"', () => { + test('warns if the NODE_ENV variable doesn\'t equal "development"', () => { const oldEnvValue = process.env.NODE_ENV; process.env.NODE_ENV = 'production'; @@ -133,7 +139,7 @@ describe('Spotlight', () => { process.env.NODE_ENV = oldEnvValue; }); - it('doesn\'t warn if the NODE_ENV variable equals "development"', () => { + test('doesn\'t warn if the NODE_ENV variable equals "development"', () => { const oldEnvValue = process.env.NODE_ENV; process.env.NODE_ENV = 'development'; @@ -147,7 +153,7 @@ describe('Spotlight', () => { process.env.NODE_ENV = oldEnvValue; }); - it('handles `process` not being available', () => { + test('handles `process` not being available', () => { const originalProcess = process; // @ts-expect-error - TS complains but we explicitly wanna test this @@ -163,7 +169,7 @@ describe('Spotlight', () => { global.process = originalProcess; }); - it('handles `process.env` not being available', () => { + test('handles `process.env` not being available', () => { const originalEnv = process.env; // @ts-expect-error - TS complains but we explicitly wanna test this diff --git a/packages/node/test/integrations/tracing/nest.test.ts b/packages/node/test/integrations/tracing/nest.test.ts index 3837e3e4ee3d..2debd036022b 100644 --- a/packages/node/test/integrations/tracing/nest.test.ts +++ b/packages/node/test/integrations/tracing/nest.test.ts @@ -1,14 +1,16 @@ import { isPatched } from '../../../src/integrations/tracing/nest/helpers'; import type { InjectableTarget } from '../../../src/integrations/tracing/nest/types'; +import { describe, expect, test } from 'vitest'; + describe('Nest', () => { describe('isPatched', () => { - it('should return true if target is already patched', () => { + test('should return true if target is already patched', () => { const target = { name: 'TestTarget', sentryPatched: true, prototype: {} }; expect(isPatched(target)).toBe(true); }); - it('should add the sentryPatched property and return false if target is not patched', () => { + test('should add the sentryPatched property and return false if target is not patched', () => { const target: InjectableTarget = { name: 'TestTarget', prototype: {} }; expect(isPatched(target)).toBe(false); expect(target.sentryPatched).toBe(true); diff --git a/packages/node/test/integrations/tracing/redis.test.ts b/packages/node/test/integrations/tracing/redis.test.ts index 57eb727964be..d02c4fd646ca 100644 --- a/packages/node/test/integrations/tracing/redis.test.ts +++ b/packages/node/test/integrations/tracing/redis.test.ts @@ -6,44 +6,46 @@ import { shouldConsiderForCache, } from '../../../src/utils/redisCache'; +import { describe, expect, test } from 'vitest'; + describe('Redis', () => { describe('getCacheKeySafely (single arg)', () => { - it('should return an empty string if there are no command arguments', () => { + test('should return an empty string if there are no command arguments', () => { const result = getCacheKeySafely('get', []); expect(result).toBe(undefined); }); - it('should return a string array representation of a single argument', () => { + test('should return a string array representation of a single argument', () => { const cmdArgs = ['key1']; const result = getCacheKeySafely('get', cmdArgs); expect(result).toStrictEqual(['key1']); }); - it('should return a string array representation of a single argument (uppercase)', () => { + test('should return a string array representation of a single argument (uppercase)', () => { const cmdArgs = ['key1']; const result = getCacheKeySafely('GET', cmdArgs); expect(result).toStrictEqual(['key1']); }); - it('should return only the key for multiple arguments', () => { + test('should return only the key for multiple arguments', () => { const cmdArgs = ['key1', 'the-value']; const result = getCacheKeySafely('get', cmdArgs); expect(result).toStrictEqual(['key1']); }); - it('should handle number arguments', () => { + test('should handle number arguments', () => { const cmdArgs = [1, 'the-value']; const result = getCacheKeySafely('get', cmdArgs); expect(result).toStrictEqual(['1']); }); - it('should handle Buffer arguments', () => { + test('should handle Buffer arguments', () => { const cmdArgs = [Buffer.from('key1'), Buffer.from('key2')]; const result = getCacheKeySafely('get', cmdArgs); expect(result).toStrictEqual(['key1']); }); - it('should return if the arg type is not supported', () => { + test('should return if the arg type is not supported', () => { const cmdArgs = [Symbol('key1')]; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore @@ -53,19 +55,19 @@ describe('Redis', () => { }); describe('getCacheKeySafely (multiple args)', () => { - it('should return a comma-separated string for multiple arguments with mget command', () => { + test('should return a comma-separated string for multiple arguments with mget command', () => { const cmdArgs = ['key1', 'key2', 'key3']; const result = getCacheKeySafely('mget', cmdArgs); expect(result).toStrictEqual(['key1', 'key2', 'key3']); }); - it('should handle Buffer arguments', () => { + test('should handle Buffer arguments', () => { const cmdArgs = [Buffer.from('key1'), Buffer.from('key2')]; const result = getCacheKeySafely('mget', cmdArgs); expect(result).toStrictEqual(['key1', 'key2']); }); - it('should handle array arguments', () => { + test('should handle array arguments', () => { const cmdArgs = [ ['key1', 'key2'], ['key3', 'key4'], @@ -74,13 +76,13 @@ describe('Redis', () => { expect(result).toStrictEqual(['key1', 'key2', 'key3', 'key4']); }); - it('should handle mixed type arguments', () => { + test('should handle mixed type arguments', () => { const cmdArgs = [Buffer.from('key1'), ['key2', 'key3'], [Buffer.from('key4'), 'key5', 'key6', 7, ['key8']]]; const result = getCacheKeySafely('mget', cmdArgs); expect(result).toStrictEqual(['key1', 'key2', 'key3', 'key4', 'key5', 'key6', '7', 'key8']); }); - it('should handle nested arrays with mixed types in arguments', () => { + test('should handle nested arrays with mixed types in arguments', () => { const cmdArgs = [ ['key1', 'key2'], ['key3', 'key4', [Buffer.from('key5'), ['key6']]], @@ -89,7 +91,7 @@ describe('Redis', () => { expect(result).toStrictEqual(['key1', 'key2', 'key3', 'key4', 'key5', 'key6']); }); - it('should return if the arg type is not supported', () => { + test('should return if the arg type is not supported', () => { const cmdArgs = [Symbol('key1')]; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore @@ -99,37 +101,37 @@ describe('Redis', () => { }); describe('calculateCacheItemSize', () => { - it('should return byte length if response is a Buffer', () => { + test('should return byte length if response is a Buffer', () => { const response = Buffer.from('test'); const result = calculateCacheItemSize(response); expect(result).toBe(response.byteLength); }); - it('should return string length if response is a string', () => { + test('should return string length if response is a string', () => { const response = 'test'; const result = calculateCacheItemSize(response); expect(result).toBe(response.length); }); - it('should return length of string representation if response is a number', () => { + test('should return length of string representation if response is a number', () => { const response = 1234; const result = calculateCacheItemSize(response); expect(result).toBe(response.toString().length); }); - it('should return 0 if response is null or undefined', () => { + test('should return 0 if response is null or undefined', () => { const response = null; const result = calculateCacheItemSize(response); expect(result).toBe(0); }); - it('should return length of JSON stringified response if response is an object', () => { + test('should return length of JSON stringified response if response is an object', () => { const response = { key: 'value' }; const result = calculateCacheItemSize(response); expect(result).toBe(JSON.stringify(response).length); }); - it('should return undefined if an error occurs', () => { + test('should return undefined if an error occurs', () => { const circularObject: { self?: any } = {}; circularObject.self = circularObject; // This will cause JSON.stringify to throw an error const result = calculateCacheItemSize(circularObject); @@ -140,7 +142,7 @@ describe('Redis', () => { describe('shouldConsiderForCache', () => { const prefixes = ['cache:', 'ioredis-cache:']; - it('should return false for non-cache commands', () => { + test('should return false for non-cache commands', () => { const command = 'EXISTS'; const commandLowercase = 'exists'; const key = ['cache:test-key']; @@ -150,28 +152,28 @@ describe('Redis', () => { expect(result2).toBe(false); }); - it('should return true for cache commands with matching prefix', () => { + test('should return true for cache commands with matching prefix', () => { const command = 'get'; const key = ['cache:test-key']; const result = shouldConsiderForCache(command, key, prefixes); expect(result).toBe(true); }); - it('should return false for cache commands without matching prefix', () => { + test('should return false for cache commands without matching prefix', () => { const command = 'get'; const key = ['test-key']; const result = shouldConsiderForCache(command, key, prefixes); expect(result).toBe(false); }); - it('should return true for multiple keys with at least one matching prefix', () => { + test('should return true for multiple keys with at least one matching prefix', () => { const command = 'mget'; const key = ['test-key', 'cache:test-key']; const result = shouldConsiderForCache(command, key, prefixes); expect(result).toBe(true); }); - it('should return false for multiple keys without any matching prefix', () => { + test('should return false for multiple keys without any matching prefix', () => { const command = 'mget'; const key = ['test-key', 'test-key2']; const result = shouldConsiderForCache(command, key, prefixes); @@ -179,7 +181,7 @@ describe('Redis', () => { }); GET_COMMANDS.concat(SET_COMMANDS).forEach(command => { - it(`should return true for ${command} command with matching prefix`, () => { + test(`should return true for ${command} command with matching prefix`, () => { const key = ['cache:test-key']; const result = shouldConsiderForCache(command, key, prefixes); expect(result).toBe(true); @@ -188,44 +190,44 @@ describe('Redis', () => { }); describe('calculateCacheItemSize', () => { - it('should return byte length for Buffer input', () => { + test('should return byte length for Buffer input', () => { const buffer = Buffer.from('test'); const result = calculateCacheItemSize(buffer); expect(result).toBe(4); }); - it('should return string length for string input', () => { + test('should return string length for string input', () => { const str = 'test'; const result = calculateCacheItemSize(str); expect(result).toBe(4); }); - it('should return number length for number input', () => { + test('should return number length for number input', () => { const num = 1234; const result = calculateCacheItemSize(num); expect(result).toBe(4); }); - it('should return 0 for null or undefined input', () => { + test('should return 0 for null or undefined input', () => { const resultForNull = calculateCacheItemSize(null); const resultForUndefined = calculateCacheItemSize(undefined); expect(resultForNull).toBe(0); expect(resultForUndefined).toBe(0); }); - it('should return total size for array input', () => { + test('should return total size for array input', () => { const arr = ['test', Buffer.from('test'), 1234]; const result = calculateCacheItemSize(arr); expect(result).toBe(12); }); - it('should return JSON string length for object input', () => { + test('should return JSON string length for object input', () => { const obj = { key: 'value' }; const result = calculateCacheItemSize(obj); expect(result).toBe(15); }); - it('should return undefined for circular objects', () => { + test('should return undefined for circular objects', () => { const circularObject: { self?: any } = {}; circularObject.self = circularObject; // This creates a circular reference const result = calculateCacheItemSize(circularObject); diff --git a/packages/node/test/sdk/api.test.ts b/packages/node/test/sdk/api.test.ts index e2699b4496f2..05416d2d7d85 100644 --- a/packages/node/test/sdk/api.test.ts +++ b/packages/node/test/sdk/api.test.ts @@ -2,13 +2,15 @@ import type { Event } from '@sentry/types'; import { getActiveSpan, getClient, startInactiveSpan, startSpan, withActiveSpan } from '../../src'; import { cleanupOtel, mockSdkInit } from '../helpers/mockSdkInit'; +import { afterEach, describe, expect, test, vi } from 'vitest'; + afterEach(() => { - jest.restoreAllMocks(); + vi.restoreAllMocks(); cleanupOtel(); }); describe('withActiveSpan()', () => { - it('should set the active span within the callback', () => { + test('should set the active span within the callback', () => { mockSdkInit(); const inactiveSpan = startInactiveSpan({ name: 'inactive-span' }); @@ -20,8 +22,8 @@ describe('withActiveSpan()', () => { }); }); - it('should create child spans when calling startSpan within the callback', async () => { - const beforeSendTransaction = jest.fn(() => null); + test('should create child spans when calling startSpan within the callback', async () => { + const beforeSendTransaction = vi.fn(() => null); mockSdkInit({ enableTracing: true, beforeSendTransaction }); const client = getClient(); @@ -55,7 +57,7 @@ describe('withActiveSpan()', () => { ); }); - it('when `null` is passed, no span should be active within the callback', () => { + test('when `null` is passed, no span should be active within the callback', () => { expect.assertions(1); startSpan({ name: 'parent-span' }, () => { withActiveSpan(null, () => { @@ -64,9 +66,9 @@ describe('withActiveSpan()', () => { }); }); - it('when `null` is passed, should start a new trace for new spans', async () => { + test('when `null` is passed, should start a new trace for new spans', async () => { const transactions: Event[] = []; - const beforeSendTransaction = jest.fn((event: Event) => { + const beforeSendTransaction = vi.fn((event: Event) => { transactions.push(event); return null; }); diff --git a/packages/node/test/sdk/client.test.ts b/packages/node/test/sdk/client.test.ts index 1aa4cf14fdff..f85ca2e37393 100644 --- a/packages/node/test/sdk/client.test.ts +++ b/packages/node/test/sdk/client.test.ts @@ -17,6 +17,8 @@ import { NodeClient, initOpenTelemetry } from '../../src'; import { getDefaultNodeClientOptions } from '../helpers/getDefaultNodeClientOptions'; import { cleanupOtel } from '../helpers/mockSdkInit'; +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; + describe('NodeClient', () => { beforeEach(() => { getIsolationScope().clear(); @@ -27,11 +29,11 @@ describe('NodeClient', () => { }); afterEach(() => { - jest.restoreAllMocks(); + vi.restoreAllMocks(); cleanupOtel(); }); - it('sets correct metadata', () => { + test('sets correct metadata', () => { const options = getDefaultNodeClientOptions(); const client = new NodeClient(options); @@ -59,7 +61,7 @@ describe('NodeClient', () => { }); }); - it('exposes a tracer', () => { + test('exposes a tracer', () => { const client = new NodeClient(getDefaultNodeClientOptions()); const tracer = client.tracer; @@ -153,31 +155,32 @@ describe('NodeClient', () => { }); }); - test('when autoSessionTracking is enabled + error occurs outside of request bounds -> requestStatus should not be set to Errored', done => { - const options = getDefaultNodeClientOptions({ autoSessionTracking: true, release: '1.4' }); - const client = new NodeClient(options); - setCurrentClient(client); - client.init(); - initOpenTelemetry(client); + test('when autoSessionTracking is enabled + error occurs outside of request bounds -> requestStatus should not be set to Errored', () => + new Promise(done => { + const options = getDefaultNodeClientOptions({ autoSessionTracking: true, release: '1.4' }); + const client = new NodeClient(options); + setCurrentClient(client); + client.init(); + initOpenTelemetry(client); - // It is required to initialise SessionFlusher to capture Session Aggregates (it is usually initialised - // by the`requestHandler`) - client.initSessionFlusher(); + // It is required to initialise SessionFlusher to capture Session Aggregates (it is usually initialised + // by the`requestHandler`) + client.initSessionFlusher(); - let isolationScope: Scope; - withIsolationScope(_isolationScope => { - _isolationScope.setRequestSession({ status: 'ok' }); - isolationScope = _isolationScope; - }); + let isolationScope: Scope; + withIsolationScope(_isolationScope => { + _isolationScope.setRequestSession({ status: 'ok' }); + isolationScope = _isolationScope; + }); - client.captureException(new Error('test exception')); + client.captureException(new Error('test exception')); - setImmediate(() => { - const requestSession = isolationScope.getRequestSession(); - expect(requestSession).toEqual({ status: 'ok' }); - done(); - }); - }); + setImmediate(() => { + const requestSession = isolationScope.getRequestSession(); + expect(requestSession).toEqual({ status: 'ok' }); + done(); + }); + })); }); describe('captureEvent()', () => { @@ -384,7 +387,7 @@ describe('NodeClient', () => { }); describe('captureCheckIn', () => { - it('sends a checkIn envelope', () => { + test('sends a checkIn envelope', () => { const options = getDefaultNodeClientOptions({ serverName: 'bar', release: '1.0.0', @@ -392,7 +395,7 @@ describe('NodeClient', () => { }); const client = new NodeClient(options); - const sendEnvelopeSpy = jest.spyOn(client, 'sendEnvelope'); + const sendEnvelopeSpy = vi.spyOn(client, 'sendEnvelope'); const id = client.captureCheckIn( { monitorSlug: 'foo', status: 'in_progress' }, @@ -454,7 +457,7 @@ describe('NodeClient', () => { ]); }); - it('sends a checkIn envelope for heartbeat checkIns', () => { + test('sends a checkIn envelope for heartbeat checkIns', () => { const options = getDefaultNodeClientOptions({ serverName: 'server', release: '1.0.0', @@ -462,7 +465,7 @@ describe('NodeClient', () => { }); const client = new NodeClient(options); - const sendEnvelopeSpy = jest.spyOn(client, 'sendEnvelope'); + const sendEnvelopeSpy = vi.spyOn(client, 'sendEnvelope'); const id = client.captureCheckIn({ monitorSlug: 'heartbeat-monitor', status: 'ok' }); @@ -484,11 +487,11 @@ describe('NodeClient', () => { ]); }); - it('does not send a checkIn envelope if disabled', () => { + test('does not send a checkIn envelope if disabled', () => { const options = getDefaultNodeClientOptions({ serverName: 'bar', enabled: false }); const client = new NodeClient(options); - const sendEnvelopeSpy = jest.spyOn(client, 'sendEnvelope'); + const sendEnvelopeSpy = vi.spyOn(client, 'sendEnvelope'); client.captureCheckIn({ monitorSlug: 'foo', status: 'in_progress' }); @@ -499,7 +502,7 @@ describe('NodeClient', () => { describe('flush/close', () => { test('client close function disables _sessionFlusher', async () => { - jest.useRealTimers(); + vi.useRealTimers(); const options = getDefaultNodeClientOptions({ autoSessionTracking: true, @@ -511,7 +514,7 @@ describe('flush/close', () => { // not due to the interval running every 60s clearInterval(client['_sessionFlusher']!['_intervalId']); - const sessionFlusherFlushFunc = jest.spyOn(SessionFlusher.prototype, 'flush'); + const sessionFlusherFlushFunc = vi.spyOn(SessionFlusher.prototype, 'flush'); const delay = 1; await client.close(delay); diff --git a/packages/node/test/sdk/init.test.ts b/packages/node/test/sdk/init.test.ts index 1f904ca528f9..2d6215483f61 100644 --- a/packages/node/test/sdk/init.test.ts +++ b/packages/node/test/sdk/init.test.ts @@ -8,6 +8,9 @@ import { init, validateOpenTelemetrySetup } from '../../src/sdk'; import { NodeClient } from '../../src/sdk/client'; import { cleanupOtel } from '../helpers/mockSdkInit'; +import type { Mock, MockInstance } from 'vitest'; +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; + // eslint-disable-next-line no-var declare var global: any; @@ -15,29 +18,29 @@ const PUBLIC_DSN = 'https://username@domain/123'; class MockIntegration implements Integration { public name: string; - public setupOnce: jest.Mock = jest.fn(); + public setupOnce: Mock = vi.fn(); public constructor(name: string) { this.name = name; } } describe('init()', () => { - let mockAutoPerformanceIntegrations: jest.SpyInstance = jest.fn(() => []); + let mockAutoPerformanceIntegrations: MockInstance = vi.fn(() => []); beforeEach(() => { global.__SENTRY__ = {}; - mockAutoPerformanceIntegrations = jest.spyOn(auto, 'getAutoPerformanceIntegrations').mockImplementation(() => []); + mockAutoPerformanceIntegrations = vi.spyOn(auto, 'getAutoPerformanceIntegrations').mockImplementation(() => []); }); afterEach(() => { cleanupOtel(); - jest.clearAllMocks(); + vi.clearAllMocks(); }); describe('integrations', () => { - it("doesn't install default integrations if told not to", () => { + test("doesn't install default integrations if told not to", () => { init({ dsn: PUBLIC_DSN, defaultIntegrations: false }); const client = getClient(); @@ -51,7 +54,7 @@ describe('init()', () => { expect(mockAutoPerformanceIntegrations).toHaveBeenCalledTimes(0); }); - it('installs merged default integrations, with overrides provided through options', () => { + test('installs merged default integrations, with overrides provided through options', () => { const mockDefaultIntegrations = [ new MockIntegration('Some mock integration 2.1'), new MockIntegration('Some mock integration 2.2'), @@ -64,14 +67,14 @@ describe('init()', () => { init({ dsn: PUBLIC_DSN, integrations: mockIntegrations, defaultIntegrations: mockDefaultIntegrations }); - expect(mockDefaultIntegrations[0]?.setupOnce as jest.Mock).toHaveBeenCalledTimes(0); - expect(mockDefaultIntegrations[1]?.setupOnce as jest.Mock).toHaveBeenCalledTimes(1); - expect(mockIntegrations[0]?.setupOnce as jest.Mock).toHaveBeenCalledTimes(1); - expect(mockIntegrations[1]?.setupOnce as jest.Mock).toHaveBeenCalledTimes(1); + expect(mockDefaultIntegrations[0]?.setupOnce as Mock).toHaveBeenCalledTimes(0); + expect(mockDefaultIntegrations[1]?.setupOnce as Mock).toHaveBeenCalledTimes(1); + expect(mockIntegrations[0]?.setupOnce as Mock).toHaveBeenCalledTimes(1); + expect(mockIntegrations[1]?.setupOnce as Mock).toHaveBeenCalledTimes(1); expect(mockAutoPerformanceIntegrations).toHaveBeenCalledTimes(0); }); - it('installs integrations returned from a callback function', () => { + test('installs integrations returned from a callback function', () => { const mockDefaultIntegrations = [ new MockIntegration('Some mock integration 3.1'), new MockIntegration('Some mock integration 3.2'), @@ -89,13 +92,13 @@ describe('init()', () => { }, }); - expect(mockDefaultIntegrations[0]?.setupOnce as jest.Mock).toHaveBeenCalledTimes(1); - expect(mockDefaultIntegrations[1]?.setupOnce as jest.Mock).toHaveBeenCalledTimes(0); - expect(newIntegration.setupOnce as jest.Mock).toHaveBeenCalledTimes(1); + expect(mockDefaultIntegrations[0]?.setupOnce as Mock).toHaveBeenCalledTimes(1); + expect(mockDefaultIntegrations[1]?.setupOnce as Mock).toHaveBeenCalledTimes(0); + expect(newIntegration.setupOnce as Mock).toHaveBeenCalledTimes(1); expect(mockAutoPerformanceIntegrations).toHaveBeenCalledTimes(0); }); - it('installs performance default instrumentations if tracing is enabled', () => { + test('installs performance default instrumentations if tracing is enabled', () => { const autoPerformanceIntegration = new MockIntegration('Some mock integration 4.4'); mockAutoPerformanceIntegrations.mockReset().mockImplementation(() => [autoPerformanceIntegration]); @@ -111,9 +114,9 @@ describe('init()', () => { enableTracing: true, }); - expect(mockIntegrations[0]?.setupOnce as jest.Mock).toHaveBeenCalledTimes(1); - expect(mockIntegrations[1]?.setupOnce as jest.Mock).toHaveBeenCalledTimes(1); - expect(autoPerformanceIntegration.setupOnce as jest.Mock).toHaveBeenCalledTimes(1); + expect(mockIntegrations[0]?.setupOnce as Mock).toHaveBeenCalledTimes(1); + expect(mockIntegrations[1]?.setupOnce as Mock).toHaveBeenCalledTimes(1); + expect(autoPerformanceIntegration.setupOnce as Mock).toHaveBeenCalledTimes(1); expect(mockAutoPerformanceIntegrations).toHaveBeenCalledTimes(1); const client = getClient(); @@ -126,7 +129,7 @@ describe('init()', () => { }); describe('OpenTelemetry', () => { - it('sets up OpenTelemetry by default', () => { + test('sets up OpenTelemetry by default', () => { init({ dsn: PUBLIC_DSN }); const client = getClient(); @@ -134,7 +137,7 @@ describe('init()', () => { expect(client?.traceProvider).toBeDefined(); }); - it('allows to opt-out of OpenTelemetry setup', () => { + test('allows to opt-out of OpenTelemetry setup', () => { init({ dsn: PUBLIC_DSN, skipOpenTelemetrySetup: true }); const client = getClient(); @@ -143,14 +146,14 @@ describe('init()', () => { }); }); - it('returns intiated client', () => { + test('returns intiated client', () => { const client = init({ dsn: PUBLIC_DSN, skipOpenTelemetrySetup: true }); expect(client).toBeInstanceOf(NodeClient); }); describe('autoSessionTracking', () => { - it('does not track session by default if no release is set', () => { + test('does not track session by default if no release is set', () => { // On CI, we always infer the release, so this does not work if (process.env.CI) { return; @@ -161,14 +164,14 @@ describe('init()', () => { expect(session).toBeUndefined(); }); - it('tracks session by default if release is set', () => { + test('tracks session by default if release is set', () => { init({ dsn: PUBLIC_DSN, release: '1.2.3' }); const session = getIsolationScope().getSession(); expect(session).toBeDefined(); }); - it('does not track session if no release is set even if autoSessionTracking=true', () => { + test('does not track session if no release is set even if autoSessionTracking=true', () => { // On CI, we always infer the release, so this does not work if (process.env.CI) { return; @@ -180,14 +183,14 @@ describe('init()', () => { expect(session).toBeUndefined(); }); - it('does not track session if autoSessionTracking=false', () => { + test('does not track session if autoSessionTracking=false', () => { init({ dsn: PUBLIC_DSN, autoSessionTracking: false, release: '1.2.3' }); const session = getIsolationScope().getSession(); expect(session).toBeUndefined(); }); - it('tracks session by default if autoSessionTracking=true & release is set', () => { + test('tracks session by default if autoSessionTracking=true & release is set', () => { init({ dsn: PUBLIC_DSN, release: '1.2.3', autoSessionTracking: true }); const session = getIsolationScope().getSession(); @@ -200,14 +203,14 @@ describe('validateOpenTelemetrySetup', () => { afterEach(() => { global.__SENTRY__ = {}; cleanupOtel(); - jest.clearAllMocks(); + vi.clearAllMocks(); }); - it('works with correct setup', () => { - const errorSpy = jest.spyOn(logger, 'error').mockImplementation(() => {}); - const warnSpy = jest.spyOn(logger, 'warn').mockImplementation(() => {}); + test('works with correct setup', () => { + const errorSpy = vi.spyOn(logger, 'error').mockImplementation(() => {}); + const warnSpy = vi.spyOn(logger, 'warn').mockImplementation(() => {}); - jest.spyOn(SentryOpentelemetry, 'openTelemetrySetupCheck').mockImplementation(() => { + vi.spyOn(SentryOpentelemetry, 'openTelemetrySetupCheck').mockImplementation(() => { return ['SentryContextManager', 'SentryPropagator', 'SentrySampler']; }); @@ -217,11 +220,11 @@ describe('validateOpenTelemetrySetup', () => { expect(warnSpy).toHaveBeenCalledTimes(0); }); - it('works with missing setup, without tracing', () => { - const errorSpy = jest.spyOn(logger, 'error').mockImplementation(() => {}); - const warnSpy = jest.spyOn(logger, 'warn').mockImplementation(() => {}); + test('works with missing setup, without tracing', () => { + const errorSpy = vi.spyOn(logger, 'error').mockImplementation(() => {}); + const warnSpy = vi.spyOn(logger, 'warn').mockImplementation(() => {}); - jest.spyOn(SentryOpentelemetry, 'openTelemetrySetupCheck').mockImplementation(() => { + vi.spyOn(SentryOpentelemetry, 'openTelemetrySetupCheck').mockImplementation(() => { return []; }); @@ -236,11 +239,11 @@ describe('validateOpenTelemetrySetup', () => { expect(warnSpy).toBeCalledWith(expect.stringContaining('You have to set up the SentrySampler.')); }); - it('works with missing setup, with tracing', () => { - const errorSpy = jest.spyOn(logger, 'error').mockImplementation(() => {}); - const warnSpy = jest.spyOn(logger, 'warn').mockImplementation(() => {}); + test('works with missing setup, with tracing', () => { + const errorSpy = vi.spyOn(logger, 'error').mockImplementation(() => {}); + const warnSpy = vi.spyOn(logger, 'warn').mockImplementation(() => {}); - jest.spyOn(SentryOpentelemetry, 'openTelemetrySetupCheck').mockImplementation(() => { + vi.spyOn(SentryOpentelemetry, 'openTelemetrySetupCheck').mockImplementation(() => { return []; }); diff --git a/packages/node/test/sdk/preload.test.ts b/packages/node/test/sdk/preload.test.ts index fedba139b0f6..aaaa3e78660e 100644 --- a/packages/node/test/sdk/preload.test.ts +++ b/packages/node/test/sdk/preload.test.ts @@ -1,28 +1,30 @@ import { logger } from '@sentry/utils'; +import { afterEach, describe, expect, test, vi } from 'vitest'; + describe('preload', () => { afterEach(() => { - jest.resetAllMocks(); + vi.resetAllMocks(); logger.disable(); delete process.env.SENTRY_DEBUG; delete process.env.SENTRY_PRELOAD_INTEGRATIONS; - jest.resetModules(); + vi.resetModules(); }); - it('works without env vars', async () => { - const logSpy = jest.spyOn(console, 'log'); + test('works without env vars', async () => { + const logSpy = vi.spyOn(console, 'log'); await import('../../src/preload'); expect(logSpy).toHaveBeenCalledTimes(0); }); - it('works with SENTRY_DEBUG set', async () => { - const logSpy = jest.spyOn(console, 'log').mockImplementation(() => {}); + test('works with SENTRY_DEBUG set', async () => { + const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); // We want to swallow these logs - jest.spyOn(console, 'debug').mockImplementation(() => {}); + vi.spyOn(console, 'debug').mockImplementation(() => {}); process.env.SENTRY_DEBUG = '1'; @@ -33,10 +35,10 @@ describe('preload', () => { expect(logSpy).toHaveBeenCalledWith('Sentry Logger [log]:', '[Sentry] Preloaded Graphql instrumentation'); }); - it('works with SENTRY_DEBUG & SENTRY_PRELOAD_INTEGRATIONS set', async () => { - const logSpy = jest.spyOn(console, 'log').mockImplementation(() => {}); + test('works with SENTRY_DEBUG & SENTRY_PRELOAD_INTEGRATIONS set', async () => { + const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); // We want to swallow these logs - jest.spyOn(console, 'debug').mockImplementation(() => {}); + vi.spyOn(console, 'debug').mockImplementation(() => {}); process.env.SENTRY_DEBUG = '1'; process.env.SENTRY_PRELOAD_INTEGRATIONS = 'Http,Express'; diff --git a/packages/node/test/sdk/scope.test.ts b/packages/node/test/sdk/scope.test.ts index 5e84e412f912..b776a5909e31 100644 --- a/packages/node/test/sdk/scope.test.ts +++ b/packages/node/test/sdk/scope.test.ts @@ -3,8 +3,10 @@ import type { Attachment, Breadcrumb, Client, ClientOptions, EventProcessor } fr import { getIsolationScope } from '../../src'; import { mockSdkInit } from '../helpers/mockSdkInit'; +import { describe, expect, test, vi } from 'vitest'; + describe('Unit | Scope', () => { - it('allows to create & update a scope', () => { + test('allows to create & update a scope', () => { const scope = new Scope(); expect(scope.getScopeData()).toEqual({ @@ -51,7 +53,7 @@ describe('Unit | Scope', () => { }); }); - it('allows to clone a scope', () => { + test('allows to clone a scope', () => { const scope = new Scope(); scope.update({ @@ -85,7 +87,7 @@ describe('Unit | Scope', () => { }); }); - it('allows to set & get a client', () => { + test('allows to set & get a client', () => { const scope = new Scope(); expect(scope.getClient()).toBeUndefined(); const client = { @@ -98,10 +100,10 @@ describe('Unit | Scope', () => { }); describe('prepareEvent', () => { - it('works without any scope data', async () => { + test('works without any scope data', async () => { mockSdkInit(); - const eventProcessor = jest.fn((a: unknown) => a) as EventProcessor; + const eventProcessor = vi.fn((a: unknown) => a) as EventProcessor; const scope = new Scope(); @@ -140,7 +142,7 @@ describe('Unit | Scope', () => { }); }); - it('merges scope data', async () => { + test('merges scope data', async () => { mockSdkInit(); const breadcrumb1 = { message: '1', timestamp: 111 } as Breadcrumb; @@ -148,9 +150,9 @@ describe('Unit | Scope', () => { const breadcrumb3 = { message: '3', timestamp: 123 } as Breadcrumb; const breadcrumb4 = { message: '4', timestamp: 333 } as Breadcrumb; - const eventProcessor1 = jest.fn((a: unknown) => a) as EventProcessor; - const eventProcessor2 = jest.fn((b: unknown) => b) as EventProcessor; - const eventProcessor3 = jest.fn((c: unknown) => c) as EventProcessor; + const eventProcessor1 = vi.fn((a: unknown) => a) as EventProcessor; + const eventProcessor2 = vi.fn((b: unknown) => b) as EventProcessor; + const eventProcessor3 = vi.fn((c: unknown) => c) as EventProcessor; const attachment1 = { filename: '1' } as Attachment; const attachment2 = { filename: '2' } as Attachment; diff --git a/packages/node/test/transports/http.test.ts b/packages/node/test/transports/http.test.ts index e945c086959a..bc9c91c82a0b 100644 --- a/packages/node/test/transports/http.test.ts +++ b/packages/node/test/transports/http.test.ts @@ -6,11 +6,19 @@ import { addItemToEnvelope, createAttachmentEnvelopeItem, createEnvelope, serial import { makeNodeTransport } from '../../src/transports'; -jest.mock('@sentry/core', () => { - const actualCore = jest.requireActual('@sentry/core'); +import type { Mock } from 'vitest'; +import { afterEach, describe, expect, test, vi } from 'vitest'; + +vi.mock('node:http', async () => { + return { __esModule: true, ...(await import('node:http')) }; +}); + +vi.mock('@sentry/core', async () => { + // eslint-disable-next-line @typescript-eslint/consistent-type-imports + const actualCore = (await vi.importActual('@sentry/core')) as typeof import('@sentry/core'); return { ...actualCore, - createTransport: jest.fn().mockImplementation(actualCore.createTransport), + createTransport: vi.fn().mockImplementation(actualCore.createTransport), }; }); @@ -78,21 +86,24 @@ const defaultOptions = { }; // empty function to keep test output clean -const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); +const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); -afterEach(done => { - jest.clearAllMocks(); +afterEach( + () => + new Promise(done => { + vi.clearAllMocks(); - if (testServer && testServer.listening) { - testServer.close(done); - } else { - done(); - } -}); + if (testServer && testServer.listening) { + testServer.close(() => done()); + } else { + done(); + } + }), +); describe('makeNewHttpTransport()', () => { describe('.send()', () => { - it('should correctly send envelope to server', async () => { + test('should correctly send envelope to server', async () => { await setupTestServer({ statusCode: SUCCESS }, (req, body) => { expect(req.method).toBe('POST'); expect(body).toBe(SERIALIZED_EVENT_ENVELOPE); @@ -102,7 +113,7 @@ describe('makeNewHttpTransport()', () => { await transport.send(EVENT_ENVELOPE); }); - it('allows overriding keepAlive', async () => { + test('allows overriding keepAlive', async () => { await setupTestServer({ statusCode: SUCCESS }, req => { expect(req.headers).toEqual( expect.objectContaining({ @@ -116,7 +127,7 @@ describe('makeNewHttpTransport()', () => { await transport.send(EVENT_ENVELOPE); }); - it('should correctly send user-provided headers to server', async () => { + test('should correctly send user-provided headers to server', async () => { await setupTestServer({ statusCode: SUCCESS }, req => { expect(req.headers).toEqual( expect.objectContaining({ @@ -138,7 +149,7 @@ describe('makeNewHttpTransport()', () => { await transport.send(EVENT_ENVELOPE); }); - it.each([RATE_LIMIT, INVALID, FAILED])( + test.each([RATE_LIMIT, INVALID, FAILED])( 'should resolve on bad server response (status %i)', async serverStatusCode => { await setupTestServer({ statusCode: serverStatusCode }); @@ -151,7 +162,7 @@ describe('makeNewHttpTransport()', () => { }, ); - it('should resolve when server responds with rate limit header and status code 200', async () => { + test('should resolve when server responds with rate limit header and status code 200', async () => { await setupTestServer({ statusCode: SUCCESS, responseHeaders: { @@ -172,7 +183,7 @@ describe('makeNewHttpTransport()', () => { }); describe('compression', () => { - it('small envelopes should not be compressed', async () => { + test('small envelopes should not be compressed', async () => { await setupTestServer( { statusCode: SUCCESS, @@ -188,7 +199,7 @@ describe('makeNewHttpTransport()', () => { await transport.send(EVENT_ENVELOPE); }); - it('large envelopes should be compressed', async () => { + test('large envelopes should be compressed', async () => { await setupTestServer( { statusCode: SUCCESS, @@ -206,12 +217,12 @@ describe('makeNewHttpTransport()', () => { }); describe('proxy', () => { - const proxyAgentSpy = jest + const proxyAgentSpy = vi .spyOn(httpProxyAgent, 'HttpsProxyAgent') // @ts-expect-error using http agent as https proxy agent .mockImplementation(() => new http.Agent({ keepAlive: false, maxSockets: 30, timeout: 2000 })); - it('can be configured through option', () => { + test('can be configured through option', () => { makeNodeTransport({ ...defaultOptions, url: 'http://9e9fd4523d784609a5fc0ebb1080592f@sentry.io:8989/mysubpath/50622', @@ -222,7 +233,7 @@ describe('makeNewHttpTransport()', () => { expect(proxyAgentSpy).toHaveBeenCalledWith('http://example.com'); }); - it('can be configured through env variables option', () => { + test('can be configured through env variables option', () => { process.env.http_proxy = 'http://example.com'; makeNodeTransport({ ...defaultOptions, @@ -234,7 +245,7 @@ describe('makeNewHttpTransport()', () => { delete process.env.http_proxy; }); - it('client options have priority over env variables', () => { + test('client options have priority over env variables', () => { process.env.http_proxy = 'http://foo.com'; makeNodeTransport({ ...defaultOptions, @@ -247,7 +258,7 @@ describe('makeNewHttpTransport()', () => { delete process.env.http_proxy; }); - it('no_proxy allows for skipping specific hosts', () => { + test('no_proxy allows for skipping specific hosts', () => { process.env.no_proxy = 'sentry.io'; makeNodeTransport({ ...defaultOptions, @@ -260,7 +271,7 @@ describe('makeNewHttpTransport()', () => { delete process.env.no_proxy; }); - it('no_proxy works with a port', () => { + test('no_proxy works with a port', () => { process.env.http_proxy = 'http://example.com:8080'; process.env.no_proxy = 'sentry.io:8989'; @@ -275,7 +286,7 @@ describe('makeNewHttpTransport()', () => { delete process.env.http_proxy; }); - it('no_proxy works with multiple comma-separated hosts', () => { + test('no_proxy works with multiple comma-separated hosts', () => { process.env.http_proxy = 'http://example.com:8080'; process.env.no_proxy = 'example.com,sentry.io,wat.com:1337'; @@ -292,14 +303,14 @@ describe('makeNewHttpTransport()', () => { }); describe('should register TransportRequestExecutor that returns the correct object from server response', () => { - it('rate limit', async () => { + test('rate limit', async () => { await setupTestServer({ statusCode: RATE_LIMIT, responseHeaders: {}, }); makeNodeTransport(defaultOptions); - const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1]; + const registeredRequestExecutor = (createTransport as Mock).mock.calls[0][1]; const executorResult = registeredRequestExecutor({ body: serializeEnvelope(EVENT_ENVELOPE), @@ -313,13 +324,13 @@ describe('makeNewHttpTransport()', () => { ); }); - it('OK', async () => { + test('OK', async () => { await setupTestServer({ statusCode: SUCCESS, }); makeNodeTransport(defaultOptions); - const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1]; + const registeredRequestExecutor = (createTransport as Mock).mock.calls[0][1]; const executorResult = registeredRequestExecutor({ body: serializeEnvelope(EVENT_ENVELOPE), @@ -337,7 +348,7 @@ describe('makeNewHttpTransport()', () => { ); }); - it('OK with rate-limit headers', async () => { + test('OK with rate-limit headers', async () => { await setupTestServer({ statusCode: SUCCESS, responseHeaders: { @@ -347,7 +358,7 @@ describe('makeNewHttpTransport()', () => { }); makeNodeTransport(defaultOptions); - const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1]; + const registeredRequestExecutor = (createTransport as Mock).mock.calls[0][1]; const executorResult = registeredRequestExecutor({ body: serializeEnvelope(EVENT_ENVELOPE), @@ -365,7 +376,7 @@ describe('makeNewHttpTransport()', () => { ); }); - it('NOK with rate-limit headers', async () => { + test('NOK with rate-limit headers', async () => { await setupTestServer({ statusCode: RATE_LIMIT, responseHeaders: { @@ -375,7 +386,7 @@ describe('makeNewHttpTransport()', () => { }); makeNodeTransport(defaultOptions); - const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1]; + const registeredRequestExecutor = (createTransport as Mock).mock.calls[0][1]; const executorResult = registeredRequestExecutor({ body: serializeEnvelope(EVENT_ENVELOPE), @@ -394,14 +405,14 @@ describe('makeNewHttpTransport()', () => { }); }); - it('should create a noop transport if an invalid url is passed', async () => { - const requestSpy = jest.spyOn(http, 'request'); + test('should create a noop transport if an invalid url is passed', async () => { + const requestSpy = vi.spyOn(http, 'request'); const transport = makeNodeTransport({ ...defaultOptions, url: 'foo' }); await transport.send(EVENT_ENVELOPE); expect(requestSpy).not.toHaveBeenCalled(); }); - it('should warn if an invalid url is passed', async () => { + test('should warn if an invalid url is passed', async () => { const transport = makeNodeTransport({ ...defaultOptions, url: 'invalid url' }); await transport.send(EVENT_ENVELOPE); expect(consoleWarnSpy).toHaveBeenCalledWith( diff --git a/packages/node/test/transports/https.test.ts b/packages/node/test/transports/https.test.ts index 8b0d3312ba54..b329cea009e2 100644 --- a/packages/node/test/transports/https.test.ts +++ b/packages/node/test/transports/https.test.ts @@ -4,15 +4,22 @@ import { createTransport } from '@sentry/core'; import type { EventEnvelope, EventItem } from '@sentry/types'; import { createEnvelope, serializeEnvelope } from '@sentry/utils'; +import type { Mock } from 'vitest'; +import { afterEach, describe, expect, test, vi } from 'vitest'; import { makeNodeTransport } from '../../src/transports'; import type { HTTPModule, HTTPModuleRequestIncomingMessage } from '../../src/transports/http-module'; import testServerCerts from './test-server-certs'; -jest.mock('@sentry/core', () => { - const actualCore = jest.requireActual('@sentry/core'); +vi.mock('node:https', async () => { + return { __esModule: true, ...(await import('node:https')) }; +}); + +vi.mock('@sentry/core', async () => { + // eslint-disable-next-line @typescript-eslint/consistent-type-imports + const actualCore = (await vi.importActual('@sentry/core')) as typeof import('@sentry/core'); return { ...actualCore, - createTransport: jest.fn().mockImplementation(actualCore.createTransport), + createTransport: vi.fn().mockImplementation(actualCore.createTransport), }; }); @@ -69,7 +76,7 @@ const EVENT_ENVELOPE = createEnvelope({ event_id: 'aa3ff046696b4b const SERIALIZED_EVENT_ENVELOPE = serializeEnvelope(EVENT_ENVELOPE); const unsafeHttpsModule: HTTPModule = { - request: jest + request: vi .fn() .mockImplementation((options: https.RequestOptions, callback?: (res: HTTPModuleRequestIncomingMessage) => void) => { return https.request({ ...options, rejectUnauthorized: false }, callback); @@ -82,19 +89,22 @@ const defaultOptions = { recordDroppedEvent: () => undefined, // noop }; -afterEach(done => { - jest.clearAllMocks(); +afterEach( + () => + new Promise(done => { + vi.clearAllMocks(); - if (testServer && testServer.listening) { - testServer.close(done); - } else { - done(); - } -}); + if (testServer && testServer.listening) { + testServer.close(() => done()); + } else { + done(); + } + }), +); describe('makeNewHttpsTransport()', () => { describe('.send()', () => { - it('should correctly send envelope to server', async () => { + test('should correctly send envelope to server', async () => { await setupTestServer({ statusCode: SUCCESS }, (req, body) => { expect(req.method).toBe('POST'); expect(body).toBe(SERIALIZED_EVENT_ENVELOPE); @@ -104,7 +114,7 @@ describe('makeNewHttpsTransport()', () => { await transport.send(EVENT_ENVELOPE); }); - it('should correctly send user-provided headers to server', async () => { + test('should correctly send user-provided headers to server', async () => { await setupTestServer({ statusCode: SUCCESS }, req => { expect(req.headers).toEqual( expect.objectContaining({ @@ -126,7 +136,7 @@ describe('makeNewHttpsTransport()', () => { await transport.send(EVENT_ENVELOPE); }); - it.each([RATE_LIMIT, INVALID, FAILED])( + test.each([RATE_LIMIT, INVALID, FAILED])( 'should resolve on bad server response (status %i)', async serverStatusCode => { await setupTestServer({ statusCode: serverStatusCode }); @@ -138,7 +148,7 @@ describe('makeNewHttpsTransport()', () => { }, ); - it('should resolve when server responds with rate limit header and status code 200', async () => { + test('should resolve when server responds with rate limit header and status code 200', async () => { await setupTestServer({ statusCode: SUCCESS, responseHeaders: { @@ -157,7 +167,7 @@ describe('makeNewHttpsTransport()', () => { }); }); - it('should use `caCerts` option', async () => { + test('should use `caCerts` option', async () => { await setupTestServer({ statusCode: SUCCESS }); const transport = makeNodeTransport({ @@ -180,12 +190,12 @@ describe('makeNewHttpsTransport()', () => { }); describe('proxy', () => { - const proxyAgentSpy = jest + const proxyAgentSpy = vi .spyOn(httpProxyAgent, 'HttpsProxyAgent') // @ts-expect-error using http agent as https proxy agent .mockImplementation(() => new http.Agent({ keepAlive: false, maxSockets: 30, timeout: 2000 })); - it('can be configured through option', () => { + test('can be configured through option', () => { makeNodeTransport({ ...defaultOptions, httpModule: unsafeHttpsModule, @@ -197,7 +207,7 @@ describe('makeNewHttpsTransport()', () => { expect(proxyAgentSpy).toHaveBeenCalledWith('https://example.com'); }); - it('can be configured through env variables option (http)', () => { + test('can be configured through env variables option (http)', () => { process.env.http_proxy = 'https://example.com'; makeNodeTransport({ ...defaultOptions, @@ -210,7 +220,7 @@ describe('makeNewHttpsTransport()', () => { delete process.env.http_proxy; }); - it('can be configured through env variables option (https)', () => { + test('can be configured through env variables option (https)', () => { process.env.https_proxy = 'https://example.com'; makeNodeTransport({ ...defaultOptions, @@ -223,7 +233,7 @@ describe('makeNewHttpsTransport()', () => { delete process.env.https_proxy; }); - it('client options have priority over env variables', () => { + test('client options have priority over env variables', () => { process.env.https_proxy = 'https://foo.com'; makeNodeTransport({ ...defaultOptions, @@ -237,7 +247,7 @@ describe('makeNewHttpsTransport()', () => { delete process.env.https_proxy; }); - it('no_proxy allows for skipping specific hosts', () => { + test('no_proxy allows for skipping specific hosts', () => { process.env.no_proxy = 'sentry.io'; makeNodeTransport({ ...defaultOptions, @@ -251,7 +261,7 @@ describe('makeNewHttpsTransport()', () => { delete process.env.no_proxy; }); - it('no_proxy works with a port', () => { + test('no_proxy works with a port', () => { process.env.http_proxy = 'https://example.com:8080'; process.env.no_proxy = 'sentry.io:8989'; @@ -267,7 +277,7 @@ describe('makeNewHttpsTransport()', () => { delete process.env.http_proxy; }); - it('no_proxy works with multiple comma-separated hosts', () => { + test('no_proxy works with multiple comma-separated hosts', () => { process.env.http_proxy = 'https://example.com:8080'; process.env.no_proxy = 'example.com,sentry.io,wat.com:1337'; @@ -284,14 +294,14 @@ describe('makeNewHttpsTransport()', () => { }); }); - it('should register TransportRequestExecutor that returns the correct object from server response (rate limit)', async () => { + test('should register TransportRequestExecutor that returns the correct object from server response (rate limit)', async () => { await setupTestServer({ statusCode: RATE_LIMIT, responseHeaders: {}, }); makeNodeTransport(defaultOptions); - const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1]; + const registeredRequestExecutor = (createTransport as Mock).mock.calls[0][1]; const executorResult = registeredRequestExecutor({ body: serializeEnvelope(EVENT_ENVELOPE), @@ -305,13 +315,13 @@ describe('makeNewHttpsTransport()', () => { ); }); - it('should register TransportRequestExecutor that returns the correct object from server response (OK)', async () => { + test('should register TransportRequestExecutor that returns the correct object from server response (OK)', async () => { await setupTestServer({ statusCode: SUCCESS, }); makeNodeTransport(defaultOptions); - const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1]; + const registeredRequestExecutor = (createTransport as Mock).mock.calls[0][1]; const executorResult = registeredRequestExecutor({ body: serializeEnvelope(EVENT_ENVELOPE), @@ -329,7 +339,7 @@ describe('makeNewHttpsTransport()', () => { ); }); - it('should register TransportRequestExecutor that returns the correct object from server response (OK with rate-limit headers)', async () => { + test('should register TransportRequestExecutor that returns the correct object from server response (OK with rate-limit headers)', async () => { await setupTestServer({ statusCode: SUCCESS, responseHeaders: { @@ -339,7 +349,7 @@ describe('makeNewHttpsTransport()', () => { }); makeNodeTransport(defaultOptions); - const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1]; + const registeredRequestExecutor = (createTransport as Mock).mock.calls[0][1]; const executorResult = registeredRequestExecutor({ body: serializeEnvelope(EVENT_ENVELOPE), @@ -357,7 +367,7 @@ describe('makeNewHttpsTransport()', () => { ); }); - it('should register TransportRequestExecutor that returns the correct object from server response (NOK with rate-limit headers)', async () => { + test('should register TransportRequestExecutor that returns the correct object from server response (NOK with rate-limit headers)', async () => { await setupTestServer({ statusCode: RATE_LIMIT, responseHeaders: { @@ -367,7 +377,7 @@ describe('makeNewHttpsTransport()', () => { }); makeNodeTransport(defaultOptions); - const registeredRequestExecutor = (createTransport as jest.Mock).mock.calls[0][1]; + const registeredRequestExecutor = (createTransport as Mock).mock.calls[0][1]; const executorResult = registeredRequestExecutor({ body: serializeEnvelope(EVENT_ENVELOPE), diff --git a/packages/node/test/utils/envToBool.test.ts b/packages/node/test/utils/envToBool.test.ts index cd06ef132267..0b8676bbcba0 100644 --- a/packages/node/test/utils/envToBool.test.ts +++ b/packages/node/test/utils/envToBool.test.ts @@ -1,7 +1,9 @@ import { envToBool } from '../../src/utils/envToBool'; +import { describe, expect, test } from 'vitest'; + describe('envToBool', () => { - it.each([ + test.each([ ['', true, null], ['', false, false], ['t', true, true], diff --git a/packages/node/test/utils/getRequestUrl.test.ts b/packages/node/test/utils/getRequestUrl.test.ts index caa92aa10a59..74aed7b4627d 100644 --- a/packages/node/test/utils/getRequestUrl.test.ts +++ b/packages/node/test/utils/getRequestUrl.test.ts @@ -2,8 +2,10 @@ import type { RequestOptions } from 'http'; import { getRequestUrl } from '../../src/utils/getRequestUrl'; +import { describe, expect, test } from 'vitest'; + describe('getRequestUrl', () => { - it.each([ + test.each([ [{ protocol: 'http:', hostname: 'localhost', port: 80 }, 'http://localhost/'], [{ protocol: 'http:', hostname: 'localhost', host: 'localhost:80', port: 80 }, 'http://localhost/'], [{ protocol: 'http:', hostname: 'localhost', port: 3000 }, 'http://localhost:3000/'], diff --git a/packages/node/tsconfig.json b/packages/node/tsconfig.json index 8f38d240197e..081a2ffd983f 100644 --- a/packages/node/tsconfig.json +++ b/packages/node/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../tsconfig.json", - "include": ["src/**/*"], + "include": ["src/**/*", "vitest.config.ts"], "compilerOptions": { "lib": ["es2018"], diff --git a/packages/node/tsconfig.test.json b/packages/node/tsconfig.test.json index 87f6afa06b86..36fdcba246db 100644 --- a/packages/node/tsconfig.test.json +++ b/packages/node/tsconfig.test.json @@ -5,7 +5,7 @@ "compilerOptions": { // should include all types from `./tsconfig.json` plus types for all test frameworks used - "types": ["node", "jest"] + "types": ["node"] // other package-specific, test-specific options } diff --git a/packages/node/vitest.config.ts b/packages/node/vitest.config.ts new file mode 100644 index 000000000000..af1e1cecad6b --- /dev/null +++ b/packages/node/vitest.config.ts @@ -0,0 +1,5 @@ +import { defineConfig } from 'vitest/config'; + +import baseConfig from '../../vite/vite.config'; + +export default defineConfig(baseConfig);