orca/src/main/browser/browser-session-registry.te...

260 lines
10 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest'
const { sessionFromPartitionMock, askForMediaAccessMock, getMediaAccessStatusMock } = vi.hoisted(
() => ({
sessionFromPartitionMock: vi.fn(),
askForMediaAccessMock: vi.fn(),
getMediaAccessStatusMock: vi.fn()
})
)
vi.mock('electron', () => ({
session: {
fromPartition: sessionFromPartitionMock
},
systemPreferences: {
askForMediaAccess: askForMediaAccessMock,
getMediaAccessStatus: getMediaAccessStatusMock
}
}))
vi.mock('./browser-manager', () => ({
browserManager: {
notifyPermissionDenied: vi.fn(),
handleGuestWillDownload: vi.fn()
}
}))
import { browserSessionRegistry } from './browser-session-registry'
import { setupClientHintsOverride } from './browser-session-ua'
import { ORCA_BROWSER_PARTITION } from '../../shared/constants'
describe('BrowserSessionRegistry', () => {
beforeEach(() => {
sessionFromPartitionMock.mockReset()
askForMediaAccessMock.mockReset()
getMediaAccessStatusMock.mockReset()
askForMediaAccessMock.mockResolvedValue(true)
getMediaAccessStatusMock.mockReturnValue('granted')
sessionFromPartitionMock.mockReturnValue({
setPermissionRequestHandler: vi.fn(),
setPermissionCheckHandler: vi.fn(),
setDisplayMediaRequestHandler: vi.fn(),
on: vi.fn(),
clearStorageData: vi.fn().mockResolvedValue(undefined),
clearCache: vi.fn().mockResolvedValue(undefined)
})
})
it('has a default profile on construction', () => {
const defaultProfile = browserSessionRegistry.getDefaultProfile()
expect(defaultProfile.id).toBe('default')
expect(defaultProfile.scope).toBe('default')
expect(defaultProfile.partition).toBe(ORCA_BROWSER_PARTITION)
})
it('allows the default partition', () => {
expect(browserSessionRegistry.isAllowedPartition(ORCA_BROWSER_PARTITION)).toBe(true)
})
it('rejects unknown partitions', () => {
expect(browserSessionRegistry.isAllowedPartition('persist:evil-partition')).toBe(false)
})
it('creates an isolated profile with a unique partition', () => {
const profile = browserSessionRegistry.createProfile('isolated', 'Test Isolated')
expect(profile).not.toBeNull()
expect(profile!.scope).toBe('isolated')
expect(profile!.partition).toMatch(/^persist:orca-browser-session-/)
expect(profile!.partition).not.toBe(ORCA_BROWSER_PARTITION)
expect(profile!.label).toBe('Test Isolated')
expect(profile!.source).toBeNull()
})
it('rejects creating a profile with scope default', () => {
const profile = browserSessionRegistry.createProfile('default', 'Sneaky')
expect(profile).toBeNull()
})
it('allows created profile partitions', () => {
const profile = browserSessionRegistry.createProfile('isolated', 'Allowed')
expect(profile).not.toBeNull()
expect(browserSessionRegistry.isAllowedPartition(profile!.partition)).toBe(true)
})
it('creates an imported profile', () => {
const profile = browserSessionRegistry.createProfile('imported', 'My Import')
expect(profile).not.toBeNull()
expect(profile!.scope).toBe('imported')
expect(profile!.partition).toMatch(/^persist:orca-browser-session-/)
})
it('resolves partition for a known profile', () => {
const profile = browserSessionRegistry.createProfile('isolated', 'Resolve Test')
expect(profile).not.toBeNull()
expect(browserSessionRegistry.resolvePartition(profile!.id)).toBe(profile!.partition)
})
it('resolves default partition for null/undefined profileId', () => {
expect(browserSessionRegistry.resolvePartition(null)).toBe(ORCA_BROWSER_PARTITION)
expect(browserSessionRegistry.resolvePartition(undefined)).toBe(ORCA_BROWSER_PARTITION)
})
it('resolves default partition for unknown profileId', () => {
expect(browserSessionRegistry.resolvePartition('nonexistent')).toBe(ORCA_BROWSER_PARTITION)
})
it('lists all profiles', () => {
const before = browserSessionRegistry.listProfiles().length
browserSessionRegistry.createProfile('isolated', 'List Test')
const after = browserSessionRegistry.listProfiles()
expect(after.length).toBe(before + 1)
})
it('updates profile source', () => {
const profile = browserSessionRegistry.createProfile('imported', 'Source Test')
expect(profile).not.toBeNull()
const updated = browserSessionRegistry.updateProfileSource(profile!.id, {
browserFamily: 'edge',
importedAt: Date.now()
})
expect(updated).not.toBeNull()
expect(updated!.source?.browserFamily).toBe('edge')
})
it('updates profile source with comet family', () => {
const profile = browserSessionRegistry.createProfile('imported', 'Comet Source Test')
expect(profile).not.toBeNull()
const updated = browserSessionRegistry.updateProfileSource(profile!.id, {
browserFamily: 'comet',
importedAt: Date.now()
})
expect(updated).not.toBeNull()
expect(updated!.source?.browserFamily).toBe('comet')
})
it('deletes a non-default profile', async () => {
const profile = browserSessionRegistry.createProfile('isolated', 'Delete Test')
expect(profile).not.toBeNull()
expect(browserSessionRegistry.isAllowedPartition(profile!.partition)).toBe(true)
const deleted = await browserSessionRegistry.deleteProfile(profile!.id)
expect(deleted).toBe(true)
expect(browserSessionRegistry.isAllowedPartition(profile!.partition)).toBe(false)
expect(browserSessionRegistry.getProfile(profile!.id)).toBeNull()
})
it('refuses to delete the default profile', async () => {
const deleted = await browserSessionRegistry.deleteProfile('default')
expect(deleted).toBe(false)
expect(browserSessionRegistry.getDefaultProfile()).not.toBeNull()
})
it('hydrates profiles from persisted data', () => {
const fakeProfile = {
id: '00000000-0000-0000-0000-000000000001',
scope: 'imported' as const,
partition: 'persist:orca-browser-session-00000000-0000-0000-0000-000000000001',
label: 'Hydrated',
source: { browserFamily: 'manual' as const, importedAt: 1000 }
}
browserSessionRegistry.hydrateFromPersisted([fakeProfile])
expect(browserSessionRegistry.getProfile('00000000-0000-0000-0000-000000000001')).not.toBeNull()
expect(browserSessionRegistry.isAllowedPartition(fakeProfile.partition)).toBe(true)
})
it('sets up session policies for new partitions', () => {
browserSessionRegistry.createProfile('isolated', 'Policy Test')
expect(sessionFromPartitionMock).toHaveBeenCalled()
const mockSession = sessionFromPartitionMock.mock.results[0]?.value
expect(mockSession?.setPermissionRequestHandler).toHaveBeenCalled()
expect(mockSession?.setPermissionCheckHandler).toHaveBeenCalled()
})
it('routes media permission requests through macOS TCC for isolated partitions', async () => {
// Why: verify the parallel fix to the default partition — isolated/imported
// profiles must also defer media permission checks to macOS instead of
// denying outright, otherwise pages inside them still hit NotAllowedError
// after the user grants Camera/Microphone to Orca.
browserSessionRegistry.createProfile('isolated', 'Media Test')
const mockSession = sessionFromPartitionMock.mock.results[0]?.value
const requestHandler = mockSession.setPermissionRequestHandler.mock.calls[0][0]
const checkHandler = mockSession.setPermissionCheckHandler.mock.calls[0][0]
const cb = vi.fn()
const guestWc = { id: 7, getURL: vi.fn(() => 'https://example.com/') }
requestHandler(guestWc, 'media', cb, { mediaTypes: ['video'] })
await vi.waitFor(() => expect(cb).toHaveBeenCalledWith(true))
expect(checkHandler(null, 'media', '', { mediaType: 'video' })).toBe(true)
expect(checkHandler(null, 'notifications', '', {})).toBe(false)
})
describe('setupClientHintsOverride', () => {
it('overrides sec-ch-ua headers for Edge UA', () => {
const onBeforeSendHeaders = vi.fn()
const mockSess = { webRequest: { onBeforeSendHeaders } } as never
const edgeUa =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.6890.3 Safari/537.36 Edg/147.0.3210.5'
setupClientHintsOverride(mockSess, edgeUa)
expect(onBeforeSendHeaders).toHaveBeenCalledWith(
{ urls: ['https://*/*'] },
expect.any(Function)
)
const callback = vi.fn()
const listener = onBeforeSendHeaders.mock.calls[0][1]
listener(
{ requestHeaders: { 'sec-ch-ua': 'old', 'sec-ch-ua-full-version-list': 'old' } },
callback
)
const modified = callback.mock.calls[0][0].requestHeaders
expect(modified['sec-ch-ua']).toContain('Microsoft Edge')
expect(modified['sec-ch-ua']).toContain('"147"')
expect(modified['sec-ch-ua-full-version-list']).toContain('147.0.3210.5')
})
it('overrides sec-ch-ua headers for Chrome UA', () => {
const onBeforeSendHeaders = vi.fn()
const mockSess = { webRequest: { onBeforeSendHeaders } } as never
const chromeUa =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.6890.3 Safari/537.36'
setupClientHintsOverride(mockSess, chromeUa)
const callback = vi.fn()
const listener = onBeforeSendHeaders.mock.calls[0][1]
listener({ requestHeaders: { 'sec-ch-ua': 'old' } }, callback)
const modified = callback.mock.calls[0][0].requestHeaders
expect(modified['sec-ch-ua']).toContain('Google Chrome')
expect(modified['sec-ch-ua']).not.toContain('Microsoft Edge')
})
it('does not register handler for non-Chrome UA', () => {
const onBeforeSendHeaders = vi.fn()
const mockSess = { webRequest: { onBeforeSendHeaders } } as never
setupClientHintsOverride(mockSess, 'Mozilla/5.0 (compatible; MSIE 10.0)')
expect(onBeforeSendHeaders).not.toHaveBeenCalled()
})
it('leaves non-Client-Hints headers unchanged', () => {
const onBeforeSendHeaders = vi.fn()
const mockSess = { webRequest: { onBeforeSendHeaders } } as never
setupClientHintsOverride(mockSess, 'Mozilla/5.0 Chrome/147.0.0.0 Safari/537.36')
const callback = vi.fn()
const listener = onBeforeSendHeaders.mock.calls[0][1]
listener(
{ requestHeaders: { Cookie: 'abc=123', 'sec-ch-ua': 'old', Accept: 'text/html' } },
callback
)
const modified = callback.mock.calls[0][0].requestHeaders
expect(modified.Cookie).toBe('abc=123')
expect(modified.Accept).toBe('text/html')
})
})
})