501 lines
15 KiB
TypeScript
501 lines
15 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import { ConfigManager } from '../src/core/ConfigManager';
|
|
import type { LightConfig, SentryEvent, ErrorEvent } from '../src/types';
|
|
|
|
// Mock the env module
|
|
vi.mock('../src/utils/env', () => ({
|
|
getContexts: vi.fn(() => ({
|
|
browser: { name: 'c', version: '120' },
|
|
os: { name: 'm', version: '14' },
|
|
device: { type: 'desktop' },
|
|
})),
|
|
getContextId: vi.fn(() => 'test_context_id'),
|
|
getPageUrl: vi.fn(() => 'https://example.com/page'),
|
|
getReferrer: vi.fn(() => 'https://google.com'),
|
|
}));
|
|
|
|
describe('ConfigManager', () => {
|
|
let configManager: ConfigManager;
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
describe('constructor', () => {
|
|
it('should create ConfigManager with valid config', () => {
|
|
const config: LightConfig = {
|
|
dsn: 'https://proj_abc123@log.example.com/1001',
|
|
release: '1.0.0',
|
|
environment: 'production',
|
|
};
|
|
configManager = new ConfigManager(config);
|
|
expect(configManager).toBeDefined();
|
|
});
|
|
|
|
it('should use default values for optional config', () => {
|
|
const config: LightConfig = {
|
|
dsn: 'https://proj_abc123@log.example.com/1001',
|
|
};
|
|
configManager = new ConfigManager(config);
|
|
expect(configManager.get('enabled')).toBe(true);
|
|
expect(configManager.get('sampleRate')).toBe(1);
|
|
expect(configManager.get('environment')).toBe('production');
|
|
});
|
|
});
|
|
|
|
describe('get/set methods', () => {
|
|
beforeEach(() => {
|
|
configManager = new ConfigManager({
|
|
dsn: 'https://proj_abc123@log.example.com/1001',
|
|
});
|
|
});
|
|
|
|
it('should get configured value', () => {
|
|
expect(configManager.get('environment')).toBe('production');
|
|
});
|
|
|
|
it('should set and get value', () => {
|
|
configManager.set('environment', 'development');
|
|
expect(configManager.get('environment')).toBe('development');
|
|
});
|
|
|
|
it('should set user info', () => {
|
|
configManager.setUser({ id: '123', username: 'test' });
|
|
const config = configManager.getAll();
|
|
expect(config.user?.id).toBe('123');
|
|
});
|
|
|
|
it('should set single tag', () => {
|
|
configManager.setTag('page', 'home');
|
|
const config = configManager.getAll();
|
|
expect(config.tags?.page).toBe('home');
|
|
});
|
|
|
|
it('should set multiple tags', () => {
|
|
configManager.setTags({ page: 'home', version: '2.0' });
|
|
const config = configManager.getAll();
|
|
expect(config.tags?.page).toBe('home');
|
|
expect(config.tags?.version).toBe('2.0');
|
|
});
|
|
|
|
it('should set extra data', () => {
|
|
configManager.setExtra('order_id', '12345');
|
|
const config = configManager.getAll();
|
|
expect(config.extra?.order_id).toBe('12345');
|
|
});
|
|
});
|
|
|
|
describe('shouldSample', () => {
|
|
it('should return true when sampleRate is 1', () => {
|
|
configManager = new ConfigManager({
|
|
dsn: 'https://proj_abc123@log.example.com/1001',
|
|
sampleRate: 1,
|
|
});
|
|
expect(configManager.shouldSample()).toBe(true);
|
|
});
|
|
|
|
it('should return false when sampleRate is 0', () => {
|
|
configManager = new ConfigManager({
|
|
dsn: 'https://proj_abc123@log.example.com/1001',
|
|
sampleRate: 0,
|
|
});
|
|
expect(configManager.shouldSample()).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('isIgnoredError', () => {
|
|
beforeEach(() => {
|
|
configManager = new ConfigManager({
|
|
dsn: 'https://proj_abc123@log.example.com/1001',
|
|
ignoreErrors: [/^Script error/i, 'ResizeObserver'],
|
|
});
|
|
});
|
|
|
|
it('should ignore matching pattern', () => {
|
|
expect(configManager.isIgnoredError('Script error')).toBe(true);
|
|
});
|
|
|
|
it('should ignore matching regex', () => {
|
|
expect(configManager.isIgnoredError('Script error: something')).toBe(true);
|
|
});
|
|
|
|
it('should not ignore non-matching message', () => {
|
|
expect(configManager.isIgnoredError('TypeError: something')).toBe(false);
|
|
});
|
|
|
|
it('should handle string pattern', () => {
|
|
expect(configManager.isIgnoredError('Some ResizeObserver issue')).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('applyToEvent', () => {
|
|
beforeEach(() => {
|
|
configManager = new ConfigManager({
|
|
dsn: 'https://proj_abc123@log.example.com/1001',
|
|
release: '1.0.0',
|
|
environment: 'production',
|
|
user: { id: '123' },
|
|
tags: { page: 'home' },
|
|
});
|
|
});
|
|
|
|
it('should apply release to event', () => {
|
|
const event: SentryEvent = {
|
|
type: 'error',
|
|
level: 'error',
|
|
timestamp: Date.now(),
|
|
} as ErrorEvent;
|
|
const result = configManager.applyToEvent(event);
|
|
expect(result.release).toBe('1.0.0');
|
|
});
|
|
|
|
it('should apply environment to event', () => {
|
|
const event: SentryEvent = {
|
|
type: 'error',
|
|
level: 'error',
|
|
timestamp: Date.now(),
|
|
} as ErrorEvent;
|
|
const result = configManager.applyToEvent(event);
|
|
expect(result.environment).toBe('production');
|
|
});
|
|
|
|
it('should apply user to event', () => {
|
|
const event: SentryEvent = {
|
|
type: 'error',
|
|
level: 'error',
|
|
timestamp: Date.now(),
|
|
} as ErrorEvent;
|
|
const result = configManager.applyToEvent(event);
|
|
expect(result.user?.id).toBe('123');
|
|
});
|
|
|
|
it('should apply and merge tags', () => {
|
|
const event: SentryEvent = {
|
|
type: 'error',
|
|
level: 'error',
|
|
timestamp: Date.now(),
|
|
tags: { custom: 'value' },
|
|
} as ErrorEvent;
|
|
const result = configManager.applyToEvent(event);
|
|
expect(result.tags?.page).toBe('home');
|
|
expect(result.tags?.custom).toBe('value');
|
|
});
|
|
|
|
it('should add context_id to event', () => {
|
|
const event: SentryEvent = {
|
|
type: 'error',
|
|
level: 'error',
|
|
timestamp: Date.now(),
|
|
} as ErrorEvent;
|
|
const result = configManager.applyToEvent(event);
|
|
expect(result.context_id).toBe('test_context_id');
|
|
});
|
|
|
|
it('should add contexts on first event', () => {
|
|
const event: SentryEvent = {
|
|
type: 'error',
|
|
level: 'error',
|
|
timestamp: Date.now(),
|
|
} as ErrorEvent;
|
|
const result = configManager.applyToEvent(event);
|
|
expect(result.contexts).toBeDefined();
|
|
expect(result.contexts?.browser).toBeDefined();
|
|
});
|
|
|
|
it('should add request info to event', () => {
|
|
const event: SentryEvent = {
|
|
type: 'error',
|
|
level: 'error',
|
|
timestamp: Date.now(),
|
|
} as ErrorEvent;
|
|
const result = configManager.applyToEvent(event);
|
|
expect(result.request?.url).toBe('https://example.com/page');
|
|
expect(result.request?.referrer).toBe('https://google.com');
|
|
});
|
|
|
|
it('should not overwrite existing request.url', () => {
|
|
const event: SentryEvent = {
|
|
type: 'error',
|
|
level: 'error',
|
|
timestamp: Date.now(),
|
|
request: { url: 'https://custom.com/api' },
|
|
} as ErrorEvent;
|
|
const result = configManager.applyToEvent(event);
|
|
expect(result.request?.url).toBe('https://custom.com/api');
|
|
});
|
|
});
|
|
|
|
describe('markContextReported', () => {
|
|
beforeEach(() => {
|
|
configManager = new ConfigManager({
|
|
dsn: 'https://proj_abc123@log.example.com/1001',
|
|
});
|
|
});
|
|
|
|
it('should add contexts on first event', () => {
|
|
configManager.markContextReported();
|
|
|
|
const event: SentryEvent = {
|
|
type: 'error',
|
|
level: 'error',
|
|
timestamp: Date.now(),
|
|
} as ErrorEvent;
|
|
const result = configManager.applyToEvent(event);
|
|
expect(result.contexts).toBeUndefined();
|
|
expect(result.context_id).toBe('test_context_id');
|
|
});
|
|
});
|
|
|
|
describe('anonymousId', () => {
|
|
beforeEach(() => {
|
|
localStorage.clear();
|
|
});
|
|
|
|
it('should add anonymousId when user.id is not set and tracking is enabled', () => {
|
|
configManager = new ConfigManager({
|
|
dsn: 'https://proj_abc123@log.example.com/1001',
|
|
trackAnonymousUsers: true,
|
|
});
|
|
const event: SentryEvent = {
|
|
type: 'error',
|
|
level: 'error',
|
|
timestamp: Date.now(),
|
|
} as ErrorEvent;
|
|
const result = configManager.applyToEvent(event);
|
|
expect((result as any).anonymousId).toBeTruthy();
|
|
expect(typeof (result as any).anonymousId).toBe('string');
|
|
});
|
|
|
|
it('should not add anonymousId when user.id is set', () => {
|
|
configManager = new ConfigManager({
|
|
dsn: 'https://proj_abc123@log.example.com/1001',
|
|
user: { id: 'user-123' },
|
|
trackAnonymousUsers: true,
|
|
});
|
|
const event: SentryEvent = {
|
|
type: 'error',
|
|
level: 'error',
|
|
timestamp: Date.now(),
|
|
} as ErrorEvent;
|
|
const result = configManager.applyToEvent(event);
|
|
expect((result as any).anonymousId).toBeUndefined();
|
|
});
|
|
|
|
it('should not add anonymousId when trackAnonymousUsers is false', () => {
|
|
configManager = new ConfigManager({
|
|
dsn: 'https://proj_abc123@log.example.com/1001',
|
|
trackAnonymousUsers: false,
|
|
});
|
|
const event: SentryEvent = {
|
|
type: 'error',
|
|
level: 'error',
|
|
timestamp: Date.now(),
|
|
} as ErrorEvent;
|
|
const result = configManager.applyToEvent(event);
|
|
expect((result as any).anonymousId).toBeUndefined();
|
|
});
|
|
|
|
it('should not add anonymousId when event has user.id', () => {
|
|
configManager = new ConfigManager({
|
|
dsn: 'https://proj_abc123@log.example.com/1001',
|
|
trackAnonymousUsers: true,
|
|
});
|
|
const event: SentryEvent = {
|
|
type: 'error',
|
|
level: 'error',
|
|
timestamp: Date.now(),
|
|
user: { id: 'event-user-id' },
|
|
} as ErrorEvent;
|
|
const result = configManager.applyToEvent(event);
|
|
expect((result as any).anonymousId).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('contextLevel', () => {
|
|
beforeEach(() => {
|
|
configManager = new ConfigManager({
|
|
dsn: 'https://proj_abc123@log.example.com/1001',
|
|
contextLevel: {
|
|
error: { maxStackFrames: 5, maxBreadcrumbs: 10 },
|
|
info: { maxStackFrames: 0, maxBreadcrumbs: 0 },
|
|
warning: { maxStackFrames: 3, maxBreadcrumbs: 5 },
|
|
},
|
|
});
|
|
});
|
|
|
|
it('should truncate stack frames for error level', () => {
|
|
const frames = Array.from({ length: 10 }, (_, i) => ({
|
|
filename: `file${i}.js`,
|
|
function: `fn${i}`,
|
|
lineno: i * 10,
|
|
colno: i,
|
|
}));
|
|
const event: SentryEvent = {
|
|
type: 'error',
|
|
level: 'error',
|
|
timestamp: Date.now(),
|
|
exception: {
|
|
type: 'Error',
|
|
value: 'test',
|
|
stacktrace: { frames },
|
|
},
|
|
} as ErrorEvent;
|
|
const result = configManager.applyToEvent(event);
|
|
expect((result as ErrorEvent).exception?.stacktrace?.frames).toHaveLength(5);
|
|
});
|
|
|
|
it('should remove exception entirely for info level (maxStackFrames=0)', () => {
|
|
const frames = [{ filename: 'file.js', function: 'fn', lineno: 1, colno: 1 }];
|
|
const event: SentryEvent = {
|
|
type: 'error',
|
|
level: 'info',
|
|
timestamp: Date.now(),
|
|
exception: {
|
|
type: 'Error',
|
|
value: 'test',
|
|
stacktrace: { frames },
|
|
},
|
|
} as ErrorEvent;
|
|
const result = configManager.applyToEvent(event);
|
|
expect((result as ErrorEvent).exception).toBeUndefined();
|
|
});
|
|
|
|
it('should truncate breadcrumbs for error level', () => {
|
|
const breadcrumbs = Array.from({ length: 20 }, (_, i) => ({
|
|
type: 'default',
|
|
message: `breadcrumb ${i}`,
|
|
timestamp: Date.now() + i,
|
|
}));
|
|
const event: SentryEvent = {
|
|
type: 'error',
|
|
level: 'error',
|
|
timestamp: Date.now(),
|
|
breadcrumbs,
|
|
} as ErrorEvent;
|
|
const result = configManager.applyToEvent(event);
|
|
expect(result.breadcrumbs).toHaveLength(10);
|
|
});
|
|
|
|
it('should remove breadcrumbs for info level (maxBreadcrumbs=0)', () => {
|
|
const breadcrumbs = [{ type: 'default', message: 'test', timestamp: Date.now() }];
|
|
const event: SentryEvent = {
|
|
type: 'error',
|
|
level: 'info',
|
|
timestamp: Date.now(),
|
|
breadcrumbs,
|
|
} as ErrorEvent;
|
|
const result = configManager.applyToEvent(event);
|
|
expect(result.breadcrumbs).toBeUndefined();
|
|
});
|
|
|
|
it('should keep last N breadcrumbs', () => {
|
|
const breadcrumbs = Array.from({ length: 20 }, (_, i) => ({
|
|
type: 'default',
|
|
message: `breadcrumb ${i}`,
|
|
timestamp: Date.now() + i,
|
|
}));
|
|
const event: SentryEvent = {
|
|
type: 'error',
|
|
level: 'warning',
|
|
timestamp: Date.now(),
|
|
breadcrumbs,
|
|
} as ErrorEvent;
|
|
const result = configManager.applyToEvent(event);
|
|
expect(result.breadcrumbs).toHaveLength(5);
|
|
// Should keep last 5
|
|
expect(result.breadcrumbs?.[0].message).toBe('breadcrumb 15');
|
|
expect(result.breadcrumbs?.[4].message).toBe('breadcrumb 19');
|
|
});
|
|
|
|
it('should not modify original event', () => {
|
|
const frames = Array.from({ length: 10 }, (_, i) => ({
|
|
filename: `file${i}.js`,
|
|
function: `fn${i}`,
|
|
lineno: i * 10,
|
|
colno: i,
|
|
}));
|
|
const event: ErrorEvent = {
|
|
type: 'error',
|
|
level: 'error',
|
|
message: 'test',
|
|
timestamp: Date.now(),
|
|
exception: {
|
|
type: 'Error',
|
|
value: 'test',
|
|
stacktrace: { frames },
|
|
},
|
|
};
|
|
configManager.applyToEvent(event);
|
|
// Original event should not be modified
|
|
expect(event.exception?.stacktrace?.frames).toHaveLength(10);
|
|
});
|
|
|
|
it('should handle event without exception', () => {
|
|
const event: SentryEvent = {
|
|
type: 'error',
|
|
level: 'error',
|
|
timestamp: Date.now(),
|
|
} as ErrorEvent;
|
|
const result = configManager.applyToEvent(event);
|
|
expect(result).toBeDefined();
|
|
});
|
|
|
|
it('should handle event without breadcrumbs', () => {
|
|
const event: SentryEvent = {
|
|
type: 'error',
|
|
level: 'error',
|
|
timestamp: Date.now(),
|
|
} as ErrorEvent;
|
|
const result = configManager.applyToEvent(event);
|
|
expect(result).toBeDefined();
|
|
});
|
|
});
|
|
|
|
describe('setUser', () => {
|
|
beforeEach(() => {
|
|
configManager = new ConfigManager({
|
|
dsn: 'https://proj_abc123@log.example.com/1001',
|
|
user: { id: 'existing-user' },
|
|
});
|
|
});
|
|
|
|
it('should set user info', () => {
|
|
configManager.setUser({ id: 'new-user', username: 'test' });
|
|
expect(configManager.get('user')).toEqual({ id: 'new-user', username: 'test' });
|
|
});
|
|
|
|
it('should clear user info when null is passed', () => {
|
|
configManager.setUser(null);
|
|
expect(configManager.get('user')).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('setTag / setTags / setExtra', () => {
|
|
beforeEach(() => {
|
|
configManager = new ConfigManager({
|
|
dsn: 'https://proj_abc123@log.example.com/1001',
|
|
});
|
|
});
|
|
|
|
it('should set tag when no tags exist', () => {
|
|
configManager.setTag('key1', 'value1');
|
|
expect(configManager.get('tags')).toEqual({ key1: 'value1' });
|
|
});
|
|
|
|
it('should merge tags', () => {
|
|
configManager.setTag('key1', 'value1');
|
|
configManager.setTags({ key2: 'value2', key3: 'value3' });
|
|
expect(configManager.get('tags')).toEqual({
|
|
key1: 'value1',
|
|
key2: 'value2',
|
|
key3: 'value3',
|
|
});
|
|
});
|
|
|
|
it('should set extra when no extra exists', () => {
|
|
configManager.setExtra('key1', 'value1');
|
|
expect(configManager.get('extra')).toEqual({ key1: 'value1' });
|
|
});
|
|
});
|
|
});
|