Files
orca/src/main/computer/macos-computer-use-permissions.test.ts
T
NeilandOrca 46646d7ff1 chore(lint): upgrade oxlint to 1.71 + enable 7 new rules (autofixed backlog) (#6841)
* chore(lint): upgrade oxlint to 1.71 and enable 7 new rules

Upgrade oxlint 1.67.0 -> 1.71.0 (1.72 was blocked by the repo's 3-day
minimum-release-age supply-chain guard; nothing here needs it). The
bump is a no-op on the existing config.

Enable 3 error rules (backlog autofixed to zero in this commit) and
4 warn rules (surface signal without gating CI):

error (autofixed, behavior-preserving):
- unicorn/prefer-node-protocol        (~1531 sites: bare builtin -> node:)
- typescript/no-import-type-side-effects (~36: all-inline-type -> import type)
- unicorn/no-array-reverse            (19: copy-then-reverse -> toReversed)

warn (real signal, current fires are test-only/correct):
- unicorn/no-array-fill-with-reference-type  (aliasing footgun guard)
- typescript/no-unsafe-function-type         (bans bare Function type)
- unicorn/prefer-array-flat-map              (map().flat() -> flatMap())
- unicorn/prefer-regexp-test                 (.match() in bool ctx -> .test())

mobile/.oxlintrc.json extends root, so it inherits all 7; the autofix
ran from root and covered mobile/ too.

Verification (all green): oxlint 0 errors (root+mobile+aux configs),
oxfmt clean, typecheck (node+cli+web), vitest 22795 passed / 0 failed,
builds (electron-vite + web + cli) succeed. node: rewrites confirmed to
skip embedded SSH/CLI string payloads (AST-only); all toReversed sites
verified to operate on fresh copies or write-once locals.

* chore(lint): bump mobile oxlint to 1.71 so inherited rules parse

mobile/ is a standalone pnpm project pinning its own oxlint@1.67, which
lacks unicorn/no-array-fill-with-reference-type (needs >=1.70). Since
mobile/.oxlintrc.json extends the root config, mobile CI's 'cd mobile &&
oxlint' failed to parse the new rule. Bump mobile to match root (1.71).

Verified in mobile/: oxlint 0 errors, oxfmt --check clean, tsc --noEmit
pass, vitest 978 passed / 0 failed.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
2026-06-29 22:38:29 -07:00

255 lines
9.2 KiB
TypeScript

import { execFileSync, spawn, spawnSync } from 'node:child_process'
import { mkdtemp, readFile, rm, stat } from 'node:fs/promises'
import { join } from 'node:path'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import {
openComputerUsePermissions,
resetComputerUsePermissions
} from './macos-computer-use-permissions'
const resolveHelperAppPathMock = vi.hoisted(() => vi.fn())
const resolveHelperExecutablePathMock = vi.hoisted(() => vi.fn())
const permissionStatusTempDir = '/tmp/orca-computer-use-permissions-test'
const helperAppPath = '/Applications/Orca Computer Use.app'
const helperInfoPlistPath = join(helperAppPath, 'Contents', 'Info.plist')
vi.mock('child_process', () => ({
execFileSync: vi.fn(),
spawn: vi.fn(() => {
const child = {
stdout: { off: vi.fn(), on: vi.fn(), setEncoding: vi.fn() },
stderr: { off: vi.fn(), on: vi.fn(), setEncoding: vi.fn() },
on: vi.fn((event: string, callback: (status: number) => void) => {
if (event === 'close') {
queueMicrotask(() => callback(0))
}
return child
}),
off: vi.fn(() => child),
unref: vi.fn()
}
return child
}),
spawnSync: vi.fn()
}))
vi.mock('fs/promises', () => ({
mkdtemp: vi.fn(),
readFile: vi.fn(),
rm: vi.fn(),
stat: vi.fn()
}))
vi.mock('./macos-native-provider-paths', () => ({
resolveMacOSComputerUseAppPath: resolveHelperAppPathMock,
resolveMacOSComputerUseExecutablePath: resolveHelperExecutablePathMock
}))
describe('openComputerUsePermissions', () => {
const originalPlatform = process.platform
beforeEach(() => {
vi.mocked(spawn).mockClear()
vi.mocked(spawnSync).mockClear()
vi.mocked(execFileSync).mockReset()
vi.mocked(mkdtemp).mockReset()
vi.mocked(readFile).mockReset()
vi.mocked(rm).mockReset()
vi.mocked(stat).mockReset()
resolveHelperAppPathMock.mockReset()
resolveHelperExecutablePathMock.mockReset()
resolveHelperExecutablePathMock.mockReturnValue(
'/Applications/Orca Computer Use.app/Contents/MacOS/orca-computer-use-macos'
)
vi.mocked(mkdtemp).mockResolvedValue(permissionStatusTempDir)
vi.mocked(stat).mockResolvedValue({} as Awaited<ReturnType<typeof stat>>)
mockPermissionStatus('{"accessibility":"granted","screenshots":"granted"}')
setPlatform('darwin')
})
afterEach(() => {
vi.useRealTimers()
setPlatform(originalPlatform)
})
it('does not launch the setup helper when all permissions are granted', async () => {
resolveHelperAppPathMock.mockReturnValue('/Applications/Orca Computer Use.app')
await expect(openComputerUsePermissions()).resolves.toEqual({
platform: 'darwin',
helperAppPath: '/Applications/Orca Computer Use.app',
permissionId: undefined,
openedSettings: false,
launchedHelper: false,
permissions: [
{ id: 'accessibility', status: 'granted' },
{ id: 'screenshots', status: 'granted' }
],
nextStep: null
})
expect(spawn).not.toHaveBeenCalledWith(
'/usr/bin/open',
['-n', '/Applications/Orca Computer Use.app', '--args', '--permissions'],
{ detached: true, stdio: 'ignore' }
)
})
it('launches the helper app in permissions mode', async () => {
resolveHelperAppPathMock.mockReturnValue('/Applications/Orca Computer Use.app')
mockPermissionStatus('{"accessibility":"granted","screenshots":"not-granted"}')
await expect(openComputerUsePermissions()).resolves.toEqual({
platform: 'darwin',
helperAppPath: '/Applications/Orca Computer Use.app',
permissionId: undefined,
openedSettings: false,
launchedHelper: true,
permissions: [
{ id: 'accessibility', status: 'granted' },
{ id: 'screenshots', status: 'not-granted' }
],
nextStep: 'Grant Screen Recording to Orca Computer Use, then retry get-app-state.'
})
expect(spawnSync).toHaveBeenCalledWith(
'/usr/bin/pkill',
['-f', 'orca-computer-use-macos[[:space:]]+--permission([[:space:]]|$)'],
{ stdio: 'ignore' }
)
expect(spawnSync).toHaveBeenCalledWith(
'/usr/bin/pkill',
['-f', 'orca-computer-use-macos[[:space:]]+--permissions([[:space:]]|$)'],
{ stdio: 'ignore' }
)
expect(spawn).toHaveBeenCalledWith(
'/usr/bin/open',
['-n', '/Applications/Orca Computer Use.app', '--args', '--permissions'],
{ detached: true, stdio: 'ignore' }
)
})
it('launches a targeted permission helper flow', async () => {
resolveHelperAppPathMock.mockReturnValue('/Applications/Orca Computer Use.app')
mockPermissionStatus('{"accessibility":"not-granted","screenshots":"not-granted"}')
await expect(openComputerUsePermissions('accessibility')).resolves.toEqual({
platform: 'darwin',
helperAppPath: '/Applications/Orca Computer Use.app',
permissionId: 'accessibility',
openedSettings: true,
launchedHelper: true,
permissions: [
{ id: 'accessibility', status: 'not-granted' },
{ id: 'screenshots', status: 'not-granted' }
],
nextStep: 'Grant Accessibility to Orca Computer Use, then retry get-app-state.'
})
expect(spawn).toHaveBeenCalledWith(
'/usr/bin/open',
['-n', '/Applications/Orca Computer Use.app', '--args', '--permission', 'accessibility'],
{ detached: true, stdio: 'ignore' }
)
})
it('launches a targeted permission helper even when that permission is already granted', async () => {
resolveHelperAppPathMock.mockReturnValue('/Applications/Orca Computer Use.app')
mockPermissionStatus('{"accessibility":"granted","screenshots":"not-granted"}')
await expect(openComputerUsePermissions('accessibility')).resolves.toEqual({
platform: 'darwin',
helperAppPath: '/Applications/Orca Computer Use.app',
permissionId: 'accessibility',
openedSettings: true,
launchedHelper: true,
permissions: [
{ id: 'accessibility', status: 'granted' },
{ id: 'screenshots', status: 'not-granted' }
],
nextStep: 'Grant Screen Recording to Orca Computer Use, then retry get-app-state.'
})
expect(spawn).toHaveBeenCalledWith(
'/usr/bin/open',
['-n', '/Applications/Orca Computer Use.app', '--args', '--permission', 'accessibility'],
{ detached: true, stdio: 'ignore' }
)
})
it('returns a no-op result on non-macOS platforms', async () => {
setPlatform('linux')
await expect(openComputerUsePermissions()).resolves.toEqual({
platform: 'linux',
helperAppPath: null,
permissionId: undefined,
openedSettings: false,
launchedHelper: false,
permissions: [
{ id: 'accessibility', status: 'unsupported' },
{ id: 'screenshots', status: 'unsupported' }
],
nextStep: null
})
expect(spawn).not.toHaveBeenCalled()
})
it('throws when the helper app is missing on macOS', async () => {
resolveHelperAppPathMock.mockReturnValue(null)
await expect(openComputerUsePermissions()).rejects.toThrow(
'Orca Computer Use.app was not found'
)
})
it('throws when the helper executable is missing during setup', async () => {
resolveHelperAppPathMock.mockReturnValue('/Applications/Orca Computer Use.app')
resolveHelperExecutablePathMock.mockReturnValue(null)
await expect(openComputerUsePermissions('accessibility')).rejects.toThrow(
'/Applications/Orca Computer Use.app/Contents/MacOS/orca-computer-use-macos was not found'
)
})
it('resets stale macOS TCC grants for the helper bundle id', async () => {
resolveHelperAppPathMock.mockReturnValue('/Applications/Orca Computer Use.app')
vi.mocked(readFile)
.mockResolvedValueOnce('{"accessibility":"granted","screenshots":"granted"}')
.mockResolvedValueOnce('{"accessibility":"not-granted","screenshots":"not-granted"}')
vi.mocked(execFileSync).mockReturnValueOnce('com.example.orca.computer-use\n')
vi.mocked(spawnSync).mockReturnValue({ status: 0 } as ReturnType<typeof spawnSync>)
await expect(resetComputerUsePermissions()).resolves.toEqual({
platform: 'darwin',
helperAppPath: '/Applications/Orca Computer Use.app',
helperUnavailableReason: null,
bundleId: 'com.example.orca.computer-use',
permissions: [
{ id: 'accessibility', status: 'not-granted' },
{ id: 'screenshots', status: 'not-granted' }
]
})
expect(execFileSync).toHaveBeenCalledWith(
'/usr/libexec/PlistBuddy',
['-c', 'Print :CFBundleIdentifier', helperInfoPlistPath],
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }
)
expect(spawnSync).toHaveBeenCalledWith(
'/usr/bin/tccutil',
['reset', 'Accessibility', 'com.example.orca.computer-use'],
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }
)
expect(spawnSync).toHaveBeenCalledWith(
'/usr/bin/tccutil',
['reset', 'ScreenCapture', 'com.example.orca.computer-use'],
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }
)
})
})
function mockPermissionStatus(json: string): void {
vi.mocked(spawnSync).mockReturnValue({ status: 0 } as ReturnType<typeof spawnSync>)
vi.mocked(readFile).mockResolvedValue(json)
}
function setPlatform(platform: NodeJS.Platform): void {
Object.defineProperty(process, 'platform', { configurable: true, value: platform })
}