posthog-node
Version:
PostHog Node.js integration
165 lines (146 loc) • 4.78 kB
text/typescript
// import { PostHog } from '../'
import { PostHog as PostHog } from '../../src/posthog-node'
import { PostHogSentryIntegration } from '../../src/extensions/sentry-integration'
jest.mock('../../src/fetch')
import fetch from '../../src/fetch'
import { waitForPromises } from 'posthog-core/test/test-utils/test-utils'
jest.mock('../../package.json', () => ({ version: '1.2.3' }))
const mockedFetch = jest.mocked(fetch, true)
const getLastBatchEvents = (): any[] | undefined => {
expect(mockedFetch).toHaveBeenCalledWith('http://example.com/batch/', expect.objectContaining({ method: 'POST' }))
// reverse mock calls array to get the last call
const call = mockedFetch.mock.calls.reverse().find((x) => (x[0] as string).includes('/batch/'))
if (!call) {
return undefined
}
return JSON.parse((call[1] as any).body as any).batch
}
const createMockSentryException = (): any => ({
exception: {
values: [
{
type: 'Error',
value: 'example error',
stacktrace: {
frames: [],
},
mechanism: { type: 'generic', handled: true },
},
],
},
event_id: '80a7023ac32c47f7acb0adaed600d149',
platform: 'node',
contexts: {},
server_name: 'localhost',
timestamp: 1704203482.356,
environment: 'production',
level: 'error',
tags: { posthog_distinct_id: 'EXAMPLE_APP_GLOBAL' },
breadcrumbs: [
{
timestamp: 1704203481.422,
category: 'console',
level: 'log',
message: '⚡: Server is running at http://localhost:8010',
},
{
timestamp: 1704203481.658,
category: 'console',
level: 'log',
message:
"PostHog Debug error [ClientError: Your personalApiKey is invalid. Are you sure you're not using your Project API key? More information: https://posthog.com/docs/api/overview]",
},
],
sdkProcessingMetadata: {
propagationContext: { traceId: 'ea26146e5a354cb0b3b1daebb3f90e33', spanId: '8d642089c3daa272' },
},
})
describe('PostHogSentryIntegration', () => {
let posthog: PostHog
let posthogSentry: PostHogSentryIntegration
jest.useFakeTimers()
beforeEach(() => {
posthog = new PostHog('TEST_API_KEY', {
host: 'http://example.com',
fetchRetryCount: 0,
})
posthogSentry = new PostHogSentryIntegration(posthog)
mockedFetch.mockResolvedValue({
status: 200,
text: () => Promise.resolve('ok'),
json: () =>
Promise.resolve({
status: 'ok',
}),
} as any)
})
afterEach(async () => {
// ensure clean shutdown & no test interdependencies
await posthog.shutdown()
})
it('should forward sentry exceptions to posthog', async () => {
expect(mockedFetch).toHaveBeenCalledTimes(0)
const mockSentry = {
getClient: () => ({
getDsn: () => ({
projectId: 123,
}),
}),
}
let processorFunction: any
posthogSentry.setupOnce(
(fn) => (processorFunction = fn),
() => mockSentry
)
processorFunction(createMockSentryException())
await waitForPromises() // First flush
jest.runOnlyPendingTimers() // Flush timer
await waitForPromises() // Second flush
const batchEvents = getLastBatchEvents()
expect(batchEvents).toEqual([
{
distinct_id: 'EXAMPLE_APP_GLOBAL',
event: '$exception',
properties: {
$exception_level: 'error',
$exception_list: [
{
mechanism: { handled: true, type: 'generic' },
stacktrace: { frames: [], type: 'raw' },
type: 'Error',
value: 'example error',
},
],
$exception_message: 'example error',
$exception_type: 'Error',
$exception_personURL: 'http://example.com/project/TEST_API_KEY/person/EXAMPLE_APP_GLOBAL',
$sentry_event_id: '80a7023ac32c47f7acb0adaed600d149',
$sentry_exception: {
values: [
{
type: 'Error',
value: 'example error',
stacktrace: { frames: [] },
mechanism: { type: 'generic', handled: true },
},
],
},
$sentry_exception_message: 'example error',
$sentry_exception_type: 'Error',
$sentry_tags: {
posthog_distinct_id: 'EXAMPLE_APP_GLOBAL',
'PostHog Person URL': 'http://example.com/project/TEST_API_KEY/person/EXAMPLE_APP_GLOBAL',
},
$lib: 'posthog-node',
$lib_version: '1.2.3',
$geoip_disable: true,
},
type: 'capture',
library: 'posthog-node',
library_version: '1.2.3',
timestamp: expect.any(String),
uuid: expect.any(String),
},
])
})
})