Files
Sauci/apps/web/app/PostHogPageView.test.ts
T

77 lines
3.1 KiB
TypeScript

import { readFile } from 'node:fs/promises'
import { describe, expect, it } from 'vitest'
import type { CapturedNetworkRequest } from 'posthog-js'
import { redactInviteNetworkRequest, redactInvitePaths } from './providers'
const pageViewPath = new URL('./PostHogPageView.tsx', import.meta.url)
const providerPath = new URL('./providers.tsx', import.meta.url)
describe('PostHog invite privacy boundary', () => {
it('does not send a global page view for invite routes', async () => {
const source = await readFile(pageViewPath, 'utf8')
expect(source).toContain("pathname?.startsWith('/join/')")
expect(source.indexOf("pathname?.startsWith('/join/')")).toBeLessThan(source.indexOf("posthog.capture('$pageview'"))
})
it('disables automatic page-leave capture and redacts join URLs', async () => {
const source = await readFile(providerPath, 'utf8')
expect(source).toContain('capture_pageview: false')
expect(source).toContain('capture_pageleave: false')
expect(source).toContain('get_current_url: redactInviteUrl')
expect(source).toContain('before_send: redactInvitePathsBeforeSend')
expect(source).toContain('maskCapturedNetworkRequestFn: redactInviteNetworkRequest')
expect(source).toContain("url.pathname.startsWith('/join/')")
expect(source).toContain('/join/[invite]')
})
it('removes an invite code from every serialized URL or path property', () => {
const inviteCode = 'A4K9BT2Q'
const event = {
event: 'join_page_viewed',
properties: {
$current_url: `https://sauci.app/join/${inviteCode}`,
$pathname: `/join/${inviteCode}`,
$referrer: `https://sauci.app/join/${inviteCode}?source=message`,
$session_entry_url: `https://sauci.app/join/${inviteCode}`,
nested: { previous_path: `/join/${inviteCode}` },
},
}
const serialized = JSON.stringify(redactInvitePaths(event))
expect(serialized).not.toContain(inviteCode)
expect(serialized).toContain('/join/[invite]')
expect(serialized).toContain('join_page_viewed')
})
it('redacts invite codes from replay network request URLs, headers, and bodies without mutating the capture', () => {
const inviteCode = 'A4K9BT2Q'
const request = {
name: `https://sauci.app/join/${inviteCode}`,
entryType: 'resource',
startTime: 1,
duration: 2,
requestHeaders: {
referer: `https://sauci.app/join/${inviteCode}`,
},
requestBody: JSON.stringify({ invite_path: `/join/${inviteCode}` }),
responseHeaders: {
location: `https://sauci.app/join/${inviteCode}`,
},
responseBody: JSON.stringify({ referrer: `https://sauci.app/join/${inviteCode}` }),
} as CapturedNetworkRequest
const redacted = redactInviteNetworkRequest(request)
const serialized = JSON.stringify(redacted)
expect(redacted).not.toBe(request)
expect(redacted.requestHeaders).not.toBe(request.requestHeaders)
expect(redacted.responseHeaders).not.toBe(request.responseHeaders)
expect(serialized).not.toContain(inviteCode)
expect(serialized).toContain('/join/[invite]')
expect(JSON.stringify(request)).toContain(inviteCode)
})
})