Skip to content

Commit

Permalink
maybe nicer
Browse files Browse the repository at this point in the history
  • Loading branch information
pauldambra committed Oct 18, 2023
1 parent 3090cd0 commit c362083
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 154 deletions.
152 changes: 62 additions & 90 deletions src/__tests__/extensions/sessionrecording.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,17 @@ import {
import { PostHogPersistence } from '../../posthog-persistence'
import {
CONSOLE_LOG_RECORDING_ENABLED_SERVER_SIDE,
SESSION_ID,
SESSION_RECORDING_ENABLED_SERVER_SIDE,
SESSION_RECORDING_RECORDER_VERSION_SERVER_SIDE,
SESSION_RECORDING_SAMPLE_RATE,
SESSION_RECORDING_SAMPLING_EXCLUDED,
} from '../../constants'
import { SessionIdManager } from '../../sessionid'
import {
INCREMENTAL_SNAPSHOT_EVENT_TYPE,
META_EVENT_TYPE,
MUTATION_SOURCE_TYPE,
} from '../../extensions/sessionrecording-utils'
import { INCREMENTAL_SNAPSHOT_EVENT_TYPE, META_EVENT_TYPE } from '../../extensions/sessionrecording-utils'
import { PostHog } from '../../posthog-core'
import { DecideResponse, PostHogConfig, Property, SessionIdChangedCallback } from '../../types'
import Mock = jest.Mock
import { uuidv7 } from '../../uuidv7'
import Mock = jest.Mock

// Type and source defined here designate a non-user-generated recording event

Expand Down Expand Up @@ -52,8 +48,8 @@ describe('SessionRecording', () => {
let session_recording_enabled_server_side: boolean
let console_log_enabled_server_side: boolean
let session_recording_sample_rate: number | undefined
let session_recording_sampling_excluded: string | null | undefined
let checkAndGetSessionAndWindowIdMock: Mock
let sessionIdGeneratorMock: Mock
let windowIdGeneratorMock: Mock

beforeEach(() => {
;(window as any).rrwebRecord = jest.fn()
Expand All @@ -66,7 +62,6 @@ describe('SessionRecording', () => {
console_log_enabled_server_side = false
session_recording_recorder_version_server_side = 'v2'
session_recording_sample_rate = undefined
session_recording_sampling_excluded = undefined

config = {
api_host: 'https://test.com',
Expand All @@ -79,15 +74,41 @@ describe('SessionRecording', () => {
persistence: 'memory',
} as unknown as PostHogConfig

checkAndGetSessionAndWindowIdMock = jest.fn()
checkAndGetSessionAndWindowIdMock.mockImplementation(() => ({
sessionId: sessionId,
windowId: 'windowId',
}))
sessionIdGeneratorMock = jest.fn().mockImplementation(() => sessionId)
windowIdGeneratorMock = jest.fn().mockImplementation(() => 'windowId')

const fakePersistence: PostHogPersistence = {
props: { SESSION_ID: sessionId },
register: jest.fn().mockImplementation((props) => {
Object.entries(props).forEach(([property_key, value]) => {
switch (property_key) {
case SESSION_RECORDING_ENABLED_SERVER_SIDE:
session_recording_enabled_server_side = <boolean>value
break
case SESSION_RECORDING_RECORDER_VERSION_SERVER_SIDE:
session_recording_recorder_version_server_side = <'v1' | 'v2' | undefined>value
break
case CONSOLE_LOG_RECORDING_ENABLED_SERVER_SIDE:
console_log_enabled_server_side = <boolean>value
break
case SESSION_RECORDING_SAMPLE_RATE:
session_recording_sample_rate = <number>value
break
case SESSION_ID:
// eslint-disable-next-line no-case-declarations
const providedId = <string>(<Array<any>>value)[1]
if (providedId !== null) {
sessionId = providedId
}
break
default:
throw new Error('config has not been mocked for this property key: ' + property_key)
}
})
}),
} as unknown as PostHogPersistence

sessionManager = {
checkAndGetSessionAndWindowId: checkAndGetSessionAndWindowIdMock,
} as unknown as SessionIdManager
sessionManager = new SessionIdManager(config, fakePersistence, sessionIdGeneratorMock, windowIdGeneratorMock)

posthog = {
get_property: (property_key: string): Property | undefined => {
Expand All @@ -100,39 +121,15 @@ describe('SessionRecording', () => {
return console_log_enabled_server_side
case SESSION_RECORDING_SAMPLE_RATE:
return session_recording_sample_rate
case SESSION_RECORDING_SAMPLING_EXCLUDED:
return session_recording_sampling_excluded
case SESSION_ID:
return sessionId
default:
throw new Error('config has not been mocked for this property key: ' + property_key)
}
},
config: config,
capture: jest.fn(),
persistence: {
register: jest.fn().mockImplementation((props) => {
Object.entries(props).forEach(([property_key, value]) => {
switch (property_key) {
case SESSION_RECORDING_ENABLED_SERVER_SIDE:
session_recording_enabled_server_side = <boolean>value
break
case SESSION_RECORDING_RECORDER_VERSION_SERVER_SIDE:
session_recording_recorder_version_server_side = <'v1' | 'v2' | undefined>value
break
case CONSOLE_LOG_RECORDING_ENABLED_SERVER_SIDE:
console_log_enabled_server_side = <boolean>value
break
case SESSION_RECORDING_SAMPLE_RATE:
session_recording_sample_rate = <number>value
break
case SESSION_RECORDING_SAMPLING_EXCLUDED:
session_recording_sampling_excluded = <string | undefined>value
break
default:
throw new Error('config has not been mocked for this property key: ' + property_key)
}
})
}),
} as unknown as PostHogPersistence,
persistence: fakePersistence,

sessionManager: sessionManager,
_addCaptureHook: jest.fn(),
Expand Down Expand Up @@ -314,13 +311,10 @@ describe('SessionRecording', () => {
sessionRecording.afterDecideResponse({
sessionRecording: { endpoint: '/s/', sampleRate: 0 },
} as unknown as DecideResponse)
expect(posthog.get_property(SESSION_RECORDING_SAMPLING_EXCLUDED)).toStrictEqual(undefined)

_emit(createIncrementalSnapshot({ data: { source: 1 } }))
expect(posthog.get_property(SESSION_RECORDING_SAMPLING_EXCLUDED)).toStrictEqual({
sessionId: sessionId,
sampled: false,
})
const { isSampled, sessionId: storedSessionId } = sessionManager.checkAndGetSessionAndWindowId(true)
expect(isSampled).toStrictEqual(false)
expect(storedSessionId).toStrictEqual(sessionId)
})

it('does emit to capture if the sample rate is 1', () => {
Expand All @@ -335,8 +329,8 @@ describe('SessionRecording', () => {
_emit(createIncrementalSnapshot({ data: { source: 1 } }))

expect(sessionRecording.emit).toBe('sampled')
expect(posthog.get_property(SESSION_RECORDING_SAMPLING_EXCLUDED)).toStrictEqual({
sampled: true,
expect(sessionManager.checkAndGetSessionAndWindowId(true)).toMatchObject({
isSampled: true,
sessionId: sessionId,
})

Expand All @@ -357,11 +351,9 @@ describe('SessionRecording', () => {
let lastSessionId = sessionRecording['sessionId']

for (let i = 0; i < 100; i++) {
// this will change the session id
checkAndGetSessionAndWindowIdMock.mockImplementation(() => ({
sessionId: 'newSessionId' + i,
windowId: 'windowId',
}))
// force change the session ID
sessionManager.resetSessionId()
sessionId = 'session-id-' + uuidv7()
_emit(createIncrementalSnapshot({ data: { source: 1 } }))

expect(sessionRecording['sessionId']).not.toBe(lastSessionId)
Expand Down Expand Up @@ -590,29 +582,27 @@ describe('SessionRecording', () => {

sessionRecording.startRecordingIfEnabled()

expect(sessionRecording.started()).toEqual(true)
expect(sessionRecording.captureStarted).toEqual(true)
expect(sessionRecording.started).toEqual(true)
expect(sessionRecording.stopRrweb).not.toEqual(undefined)

sessionRecording.stopRecording()

expect(sessionRecording.stopRrweb).toEqual(undefined)
expect(sessionRecording.captureStarted).toEqual(false)
expect(sessionRecording.started).toEqual(false)
})

it('session recording can be turned on after being turned off', () => {
expect(sessionRecording.stopRrweb).toEqual(undefined)

sessionRecording.startRecordingIfEnabled()

expect(sessionRecording.started()).toEqual(true)
expect(sessionRecording.captureStarted).toEqual(true)
expect(sessionRecording.started).toEqual(true)
expect(sessionRecording.stopRrweb).not.toEqual(undefined)

sessionRecording.stopRecording()

expect(sessionRecording.stopRrweb).toEqual(undefined)
expect(sessionRecording.captureStarted).toEqual(false)
expect(sessionRecording.started).toEqual(false)
})

describe('console logs', () => {
Expand Down Expand Up @@ -643,50 +633,32 @@ describe('SessionRecording', () => {
})

it('sends a full snapshot if there is a new session/window id and the event is not type FullSnapshot or Meta', () => {
checkAndGetSessionAndWindowIdMock.mockImplementation(() => ({
sessionId: 'new-session-id',
windowId: 'new-window-id',
}))
sessionIdGeneratorMock.mockImplementation(() => 'newSessionId')
windowIdGeneratorMock.mockImplementation(() => 'newWindowId')
_emit(createIncrementalSnapshot())
expect((window as any).rrwebRecord.takeFullSnapshot).toHaveBeenCalled()
})

it('sends a full snapshot if there is a new window id and the event is not type FullSnapshot or Meta', () => {
checkAndGetSessionAndWindowIdMock.mockImplementation(() => ({
sessionId: 'old-session-id',
windowId: 'new-window-id',
}))
sessionIdGeneratorMock.mockImplementation(() => 'old-session-id')
windowIdGeneratorMock.mockImplementation(() => 'newWindowId')
_emit(createIncrementalSnapshot())
expect((window as any).rrwebRecord.takeFullSnapshot).toHaveBeenCalled()
})

it('does not send a full snapshot if there is a new session/window id and the event is type FullSnapshot or Meta', () => {
checkAndGetSessionAndWindowIdMock.mockImplementation(() => ({
sessionId: 'new-session-id',
windowId: 'new-window-id',
}))
sessionIdGeneratorMock.mockImplementation(() => 'newSessionId')
windowIdGeneratorMock.mockImplementation(() => 'newWindowId')
_emit(createIncrementalSnapshot({ type: META_EVENT_TYPE }))
expect((window as any).rrwebRecord.takeFullSnapshot).not.toHaveBeenCalled()
})

it('does not send a full snapshot if there is not a new session or window id', () => {
checkAndGetSessionAndWindowIdMock.mockImplementation(() => ({
sessionId: 'old-session-id',
windowId: 'old-window-id',
}))
sessionIdGeneratorMock.mockImplementation(() => 'old-session-id')
windowIdGeneratorMock.mockImplementation(() => 'old-window-id')
_emit(createIncrementalSnapshot())
expect((window as any).rrwebRecord.takeFullSnapshot).not.toHaveBeenCalled()
})

it('it calls checkAndGetSessionAndWindowId with readOnly as true if it not a user interaction', () => {
_emit(createIncrementalSnapshot({ data: { source: MUTATION_SOURCE_TYPE, adds: [{ id: 1 }] } }))
expect(checkAndGetSessionAndWindowIdMock).toHaveBeenCalledWith(true, undefined)
})

it('it calls checkAndGetSessionAndWindowId with readOnly as false if it is a user interaction', () => {
_emit(createIncrementalSnapshot())
expect(checkAndGetSessionAndWindowIdMock).toHaveBeenCalledWith(false, undefined)
})
})

describe('the session id manager', () => {
Expand Down
1 change: 0 additions & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ export const SESSION_RECORDING_ENABLED_SERVER_SIDE = '$session_recording_enabled
export const CONSOLE_LOG_RECORDING_ENABLED_SERVER_SIDE = '$console_log_recording_enabled_server_side'
export const SESSION_RECORDING_RECORDER_VERSION_SERVER_SIDE = '$session_recording_recorder_version_server_side' // follows rrweb versioning
export const SESSION_RECORDING_SAMPLE_RATE = '$session_recording_sample_rate'
export const SESSION_RECORDING_SAMPLING_EXCLUDED = '$session_recording_sampling_excluded'
export const SESSION_ID = '$sesid'
export const ENABLED_FEATURE_FLAGS = '$enabled_feature_flags'
export const PERSISTENCE_EARLY_ACCESS_FEATURES = '$early_access_features'
Expand Down
Loading

0 comments on commit c362083

Please sign in to comment.