import { describe, it, expect, vi, beforeEach } from 'vitest'; import SamplingPlugin from '../src/plugins/SamplingPlugin'; import type { LightClient, LightConfig, SentryEvent, ErrorEvent } from '../src/types'; describe('SamplingPlugin', () => { function createMockClient(configOverrides: Partial = {}): LightClient { const config: LightConfig = { dsn: 'https://abc@sentry.io/123', sampleRate: 1, sampling: { rates: { error: 1, performance: 1, network: 1, behavior: 1, }, }, ...configOverrides, } as LightConfig; return { config, dsn: { protocol: 'https', publicKey: 'abc', host: 'sentry.io', projectId: '123' }, on: vi.fn(), off: vi.fn(), emit: vi.fn(), captureException: vi.fn(), captureMessage: vi.fn(), captureEvent: vi.fn(), setUser: vi.fn(), setAnonymousId: vi.fn(), setTag: vi.fn(), setTags: vi.fn(), setExtra: vi.fn(), addBreadcrumb: vi.fn(), startTransaction: vi.fn(), getCurrentTransaction: vi.fn(), getCurrentSpan: vi.fn(), setSpan: vi.fn(), startSpan: vi.fn(), flush: vi.fn(), disable: vi.fn(), enable: vi.fn(), use: vi.fn(), init: vi.fn(), destroy: vi.fn(), } as unknown as LightClient; } function createErrorEvent(): ErrorEvent { return { type: 'error', level: 'error', message: 'Test error', timestamp: Date.now(), }; } function createEvent(type: string): SentryEvent { return { type: type as any, level: 'info', timestamp: Date.now(), } as SentryEvent; } describe('constructor', () => { it('should create plugin with correct name and version', () => { const plugin = new SamplingPlugin(); expect(plugin.name).toBe('sampling'); expect(plugin.version).toBe('1.0.0'); }); }); describe('setup', () => { it('should call setup with client', () => { const plugin = new SamplingPlugin(); const client = createMockClient(); expect(() => plugin.setup(client)).not.toThrow(); }); it('should load default rates when no sampling config', () => { const plugin = new SamplingPlugin(); const client = createMockClient({ sampling: undefined }); plugin.setup(client); const event = createErrorEvent(); const result = plugin.beforeReport(event); expect(result).not.toBeNull(); }); it('should load custom rates from config', () => { const plugin = new SamplingPlugin(); const client = createMockClient({ sampleRate: 1, sampling: { rates: { error: 0.5, performance: 0.1, }, }, }); plugin.setup(client); const randomSpy = vi.spyOn(Math, 'random').mockReturnValue(0.1); expect(plugin.beforeReport(createEvent('error'))).toBeTruthy(); randomSpy.mockRestore(); }); }); describe('beforeReport', () => { it('should return event when sampling rate is 1', () => { const plugin = new SamplingPlugin(); const client = createMockClient({ sampleRate: 1 }); plugin.setup(client); const event = createErrorEvent(); const result = plugin.beforeReport(event); expect(result).toBe(event); }); it('should return null when global sampleRate is 0', () => { const plugin = new SamplingPlugin(); const client = createMockClient({ sampleRate: 0 }); plugin.setup(client); const event = createErrorEvent(); const result = plugin.beforeReport(event); expect(result).toBeNull(); }); it('should return null when global sampleRate is negative', () => { const plugin = new SamplingPlugin(); const client = createMockClient({ sampleRate: -1 }); plugin.setup(client); const event = createErrorEvent(); const result = plugin.beforeReport(event); expect(result).toBeNull(); }); it('should return event when type rate is 1', () => { const plugin = new SamplingPlugin(); const client = createMockClient({ sampleRate: 1, sampling: { rates: { error: 1 } }, }); plugin.setup(client); const event = createErrorEvent(); const result = plugin.beforeReport(event); expect(result).toBe(event); }); it('should return null when type rate is 0', () => { const plugin = new SamplingPlugin(); const client = createMockClient({ sampleRate: 1, sampling: { rates: { error: 0 } }, }); plugin.setup(client); const event = createErrorEvent(); const result = plugin.beforeReport(event); expect(result).toBeNull(); }); it('should always sample count events (rate = 1)', () => { const plugin = new SamplingPlugin(); const client = createMockClient({ sampleRate: 0, sampling: { rates: { error: 0 } }, }); plugin.setup(client); const countEvent = { type: 'count' as const, level: 'info' as const, timestamp: Date.now(), fingerprint: 'test', count: 1, ts_start: Date.now(), ts_end: Date.now(), }; const result = plugin.beforeReport(countEvent); expect(result).toBeNull(); }); it('should handle unknown event types (default rate = 1)', () => { const plugin = new SamplingPlugin(); const client = createMockClient({ sampleRate: 1 }); plugin.setup(client); const unknownEvent = createEvent('unknown_type'); const result = plugin.beforeReport(unknownEvent); expect(result).not.toBeNull(); }); it('should use combined rate when globalRate < 1', () => { const plugin = new SamplingPlugin(); const client = createMockClient({ sampleRate: 0.5, sampling: { rates: { error: 0.5 } }, }); plugin.setup(client); const randomSpy = vi.spyOn(Math, 'random').mockReturnValue(0.9); const event = createErrorEvent(); const result = plugin.beforeReport(event); expect(result).toBeNull(); randomSpy.mockRestore(); }); it('should pass when random is below combined rate', () => { const plugin = new SamplingPlugin(); const client = createMockClient({ sampleRate: 0.5, sampling: { rates: { error: 0.5 } }, }); plugin.setup(client); const randomSpy = vi.spyOn(Math, 'random').mockReturnValue(0.1); const event = createErrorEvent(); const result = plugin.beforeReport(event); expect(result).toBe(event); randomSpy.mockRestore(); }); it('should pass when type rate is 1 and globalRate is 1', () => { const plugin = new SamplingPlugin(); const client = createMockClient({ sampleRate: 1 }); plugin.setup(client); const randomSpy = vi.spyOn(Math, 'random').mockReturnValue(0.99); const event = createErrorEvent(); const result = plugin.beforeReport(event); expect(result).toBe(event); randomSpy.mockRestore(); }); }); describe('destroy', () => { it('should not throw when destroy is called', () => { const plugin = new SamplingPlugin(); expect(() => plugin.destroy()).not.toThrow(); }); }); describe('config loading', () => { it('should handle missing sampling config', () => { const plugin = new SamplingPlugin(); const client = createMockClient({ sampling: undefined }); plugin.setup(client); const event = createErrorEvent(); const result = plugin.beforeReport(event); expect(result).not.toBeNull(); }); it('should handle sampling config without rates', () => { const plugin = new SamplingPlugin(); const client = createMockClient({ sampling: {} }); plugin.setup(client); const event = createErrorEvent(); const result = plugin.beforeReport(event); expect(result).not.toBeNull(); }); it('should merge custom rates with defaults', () => { const plugin = new SamplingPlugin(); const client = createMockClient({ sampling: { rates: { error: 0.5, }, }, }); plugin.setup(client); const randomSpy = vi.spyOn(Math, 'random').mockReturnValue(0.9); expect(plugin.beforeReport(createEvent('error'))).toBeNull(); expect(plugin.beforeReport(createEvent('performance'))).not.toBeNull(); randomSpy.mockRestore(); }); }); });