mirror of
https://github.com/stablyai/orca.git
synced 2026-10-12 16:03:03 +00:00
* refactor(tests): split oversized test files off the max-lines suppression list Every `*.test.ts`/`*.spec.ts` that carried an `eslint/oxlint-disable max-lines` directive is now split into focused, behavior-scoped suites that fit the 800-line test budget, with shared setup extracted into co-located `*-test-harness.ts` / `*-test-fixtures.ts` modules (300-line budget). 83 files became ~930; the largest output is 797 effective lines. `orca-runtime.test.ts` is intentionally untouched. Test bodies were moved by scripted line-range slicing rather than retyped, so assertions are byte-identical. The only permitted body edits were mechanical rebinding where a shared value moved into a harness (e.g. `tmpHome` -> `homes.tmpHome`). Registries that enumerate test files were updated in lockstep: - config/max-lines-baseline.txt: pruned 341 -> 258 entries (all 83 removed). - config/reliability-gates.jsonc: 33 gates repointed at the split files, with assertionRefs split per file where a gate's coverage now spans several. - .github/workflows/pr.yml: the real-zsh lane now lists the 4 split files that actually exercise zsh, so they keep running in the dedicated shell lane. Also renamed agent-hooks `server-test-fixtures.ts` to `server.test-fixtures.ts` so the global-fetch call-site audit keeps skipping it, and added `.js` extensions to the CLI suites' dynamic harness imports (node16 resolution) to unbreak `build:cli`. Verification: full suite 52,449 passing vs 52,448 at baseline with zero assertions lost; `pnpm lint`, `pnpm typecheck`, and `pnpm build:cli` all exit 0; the terminal-pane e2e spec runs 31/31 headless. * refactor(tests): split hook-idle arbitration suite that oxfmt pushed over budget The pre-commit oxfmt pass reflowed pty-connection-hook-idle-arbitration.test.ts to 811 effective lines, 11 over the test budget. Split the hook-completion side effect and replacement-agent veto cases into their own suite; both files now sit well under the cap and the 15 tests are unchanged. * test: port upstream test changes into the split files after rebase Rebasing onto main surfaced 27 tests that main had added to files this branch deleted, plus edits to tests that had already moved. Taking the deletion side of those modify/delete conflicts would have dropped that coverage silently, so each upstream change is ported into the split file that now owns the behavior — for example main's six orchestration mailbox tests land across orchestration-runs, -send, and -check. Also repoints `orchestration.notification-mailbox-consistency`, a gate main added after this branch's gate remap, at those same three split files, and re-prunes the max-lines baseline against main's (257 entries). Verified: all 27 upstream test titles present; full suite 52,761 passing with the only diff vs baseline being 12 tests main itself removed and 3 that moved from skipped to passing; lint and typecheck exit 0. * fix(test): flush pending continuations before tearing down terminal test globals CI shard 5/16 failed on both Node 24 and 26 with `ReferenceError: window is not defined` from pty-connection.ts, surfacing through pty-connection-daemon-snapshot-replay.test.ts. The reattach/settle chains `await` a real promise and then touch `window.api`. Under fake timers those continuations cannot run, so they only become schedulable once restoreTerminalTestGlobals() switches back to real timers — which previously happened immediately before `delete globalThis.window`, so a late continuation threw and failed the whole file. Flush async ticks in that window instead. This is latent in the source rather than new: the pre-split 25k-line file kept running other tests after these, which gave the chains time to settle before teardown. Splitting the file moved teardown directly behind them. * fix(test): keep an inert window after terminal test teardown instead of deleting it The async-tick flush was not enough: the reattach/settle chain can resolve after teardown regardless of how long we drain, so CI shard 5/16 still failed with `ReferenceError: window is not defined` from pty-connection.ts. A real renderer never loses `window`, so deleting it was the artificial part. Swap in an inert proxy whose properties resolve to callables and whose calls resolve to undefined, making a late `window.api.pty.*` call a harmless no-op. The next test replaces it wholesale via installTerminalTestGlobals(), and no test asserts that `window` is absent.
555 lines
23 KiB
TypeScript
555 lines
23 KiB
TypeScript
import { lstat, mkdir, readFile, readlink, symlink, writeFile } from 'node:fs/promises'
|
|
import { tmpdir } from 'node:os'
|
|
import { dirname, join } from 'node:path'
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
const execFileMock = vi.hoisted(() => vi.fn())
|
|
|
|
vi.mock('electron', () => ({
|
|
app: {
|
|
isPackaged: false,
|
|
getPath: () => tmpdir(),
|
|
getAppPath: () => tmpdir()
|
|
}
|
|
}))
|
|
|
|
vi.mock('node:child_process', () => ({
|
|
execFile: execFileMock
|
|
}))
|
|
|
|
import { CliInstaller } from './cli-installer'
|
|
import { createPackagedMacLauncher, makeFixture } from './cli-installer-test-fixtures'
|
|
|
|
describe('CliInstaller', () => {
|
|
beforeEach(() => {
|
|
execFileMock.mockReset()
|
|
})
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers()
|
|
vi.restoreAllMocks()
|
|
})
|
|
|
|
// Why: on Apple Silicon, /usr/local/bin does not exist by default. The installer
|
|
// must fall back to ~/.local/bin (user-writable, no sudo) rather than failing
|
|
// silently when the parent directory is absent.
|
|
it.skipIf(process.platform === 'win32')(
|
|
'falls back to ~/.local/bin/orca on macOS when /usr/local/bin does not exist',
|
|
async () => {
|
|
const fixture = await makeFixture()
|
|
const homePath = join(fixture.root, 'home')
|
|
const resourcesPath = await createPackagedMacLauncher(fixture.root)
|
|
// Simulate arm64: point defaultMacCommandPath at a dir that does not exist
|
|
// in the fixture so existsSync(dirname(...)) returns false.
|
|
const absentUsrLocalBin = join(fixture.root, 'usr', 'local', 'bin', 'orca')
|
|
const installer = new CliInstaller({
|
|
platform: 'darwin',
|
|
isPackaged: true,
|
|
resourcesPath,
|
|
userDataPath: fixture.userDataPath,
|
|
execPath: '/Applications/Orca.app/Contents/MacOS/Orca',
|
|
appPath: fixture.appPath,
|
|
homePath,
|
|
defaultMacCommandPath: absentUsrLocalBin,
|
|
processPathEnv: join(homePath, '.local', 'bin')
|
|
})
|
|
|
|
const status = await installer.getStatus()
|
|
expect(status.commandPath).toBe(join(homePath, '.local', 'bin', 'orca'))
|
|
expect(status.state).toBe('not_installed')
|
|
expect(status.supported).toBe(true)
|
|
|
|
const installed = await installer.install()
|
|
expect(installed.state).toBe('installed')
|
|
expect(installed.commandPath).toBe(join(homePath, '.local', 'bin', 'orca'))
|
|
expect(installed.pathConfigured).toBe(true)
|
|
}
|
|
)
|
|
|
|
// Why: on Intel Macs /usr/local/bin exists, so the installer must keep using
|
|
// it as the canonical path and not regress to ~/.local/bin.
|
|
it.skipIf(process.platform === 'win32')(
|
|
'uses /usr/local/bin/orca on macOS when /usr/local/bin exists',
|
|
async () => {
|
|
const fixture = await makeFixture()
|
|
const resourcesPath = await createPackagedMacLauncher(fixture.root)
|
|
const usrLocalBin = join(fixture.root, 'usr', 'local', 'bin')
|
|
await mkdir(usrLocalBin, { recursive: true })
|
|
|
|
const installPath = join(usrLocalBin, 'orca')
|
|
const installer = new CliInstaller({
|
|
platform: 'darwin',
|
|
isPackaged: true,
|
|
resourcesPath,
|
|
userDataPath: fixture.userDataPath,
|
|
execPath: '/Applications/Orca.app/Contents/MacOS/Orca',
|
|
appPath: fixture.appPath,
|
|
defaultMacCommandPath: installPath,
|
|
processPathEnv: usrLocalBin
|
|
})
|
|
|
|
const installed = await installer.install()
|
|
expect(installed.state).toBe('installed')
|
|
expect(installed.commandPath).toBe(installPath)
|
|
expect(installed.pathConfigured).toBe(true)
|
|
}
|
|
)
|
|
|
|
// Why: users can have a managed Orca command in ~/.local/bin even when
|
|
// /usr/local/bin exists; Settings must follow the shell-visible command.
|
|
it.skipIf(process.platform === 'win32')(
|
|
'uses an existing managed macOS orca command from the shell PATH before /usr/local/bin',
|
|
async () => {
|
|
const fixture = await makeFixture()
|
|
const homePath = join(fixture.root, 'home')
|
|
const resourcesPath = await createPackagedMacLauncher(fixture.root)
|
|
const usrLocalBin = join(fixture.root, 'usr', 'local', 'bin')
|
|
const userLocalBin = join(homePath, '.local', 'bin')
|
|
const defaultInstallPath = join(usrLocalBin, 'orca')
|
|
const userInstallPath = join(userLocalBin, 'orca')
|
|
const launcherPath = join(resourcesPath, 'bin', 'orca')
|
|
await mkdir(usrLocalBin, { recursive: true })
|
|
await mkdir(userLocalBin, { recursive: true })
|
|
await symlink(launcherPath, userInstallPath)
|
|
|
|
const installer = new CliInstaller({
|
|
platform: 'darwin',
|
|
isPackaged: true,
|
|
resourcesPath,
|
|
userDataPath: fixture.userDataPath,
|
|
execPath: '/Applications/Orca.app/Contents/MacOS/Orca',
|
|
appPath: fixture.appPath,
|
|
homePath,
|
|
defaultMacCommandPath: defaultInstallPath,
|
|
processPathEnv: `${userLocalBin}:${usrLocalBin}`
|
|
})
|
|
|
|
const status = await installer.getStatus()
|
|
expect(status.state).toBe('installed')
|
|
expect(status.commandPath).toBe(userInstallPath)
|
|
expect(status.pathConfigured).toBe(true)
|
|
|
|
const installed = await installer.install()
|
|
expect(installed.commandPath).toBe(userInstallPath)
|
|
await expect(readlink(userInstallPath)).resolves.toBe(launcherPath)
|
|
await expect(lstat(defaultInstallPath)).rejects.toMatchObject({ code: 'ENOENT' })
|
|
}
|
|
)
|
|
|
|
// Why: POSIX command lookup skips broken symlinks and keeps searching PATH,
|
|
// so a stale earlier artifact must not steal status from the install path.
|
|
it.skipIf(process.platform === 'win32')(
|
|
'skips a broken managed macOS orca symlink before /usr/local/bin',
|
|
async () => {
|
|
const fixture = await makeFixture()
|
|
const homePath = join(fixture.root, 'home')
|
|
const resourcesPath = await createPackagedMacLauncher(fixture.root)
|
|
const usrLocalBin = join(fixture.root, 'usr', 'local', 'bin')
|
|
const userLocalBin = join(homePath, '.local', 'bin')
|
|
const defaultInstallPath = join(usrLocalBin, 'orca')
|
|
const userInstallPath = join(userLocalBin, 'orca')
|
|
const launcherPath = join(resourcesPath, 'bin', 'orca')
|
|
const oldLauncherPath = join(fixture.root, 'Old.app', 'Contents', 'Resources', 'bin', 'orca')
|
|
await mkdir(usrLocalBin, { recursive: true })
|
|
await mkdir(userLocalBin, { recursive: true })
|
|
await symlink(oldLauncherPath, userInstallPath)
|
|
|
|
const installer = new CliInstaller({
|
|
platform: 'darwin',
|
|
isPackaged: true,
|
|
resourcesPath,
|
|
userDataPath: fixture.userDataPath,
|
|
execPath: '/Applications/Orca.app/Contents/MacOS/Orca',
|
|
appPath: fixture.appPath,
|
|
homePath,
|
|
defaultMacCommandPath: defaultInstallPath,
|
|
processPathEnv: `${userLocalBin}:${usrLocalBin}`
|
|
})
|
|
|
|
const status = await installer.getStatus()
|
|
expect(status).toMatchObject({
|
|
commandPath: defaultInstallPath,
|
|
state: 'not_installed',
|
|
currentTarget: null
|
|
})
|
|
|
|
const installed = await installer.install()
|
|
expect(installed.commandPath).toBe(defaultInstallPath)
|
|
expect(installed.state).toBe('installed')
|
|
await expect(readlink(defaultInstallPath)).resolves.toBe(launcherPath)
|
|
await expect(readlink(userInstallPath)).resolves.toBe(oldLauncherPath)
|
|
}
|
|
)
|
|
|
|
// Why: PATH lookup stops at the first existing command; a later managed
|
|
// ~/.local/bin/orca must not steal status from /usr/local/bin/orca.
|
|
it.skipIf(process.platform === 'win32')(
|
|
'keeps the default macOS command when a managed orca appears later on PATH',
|
|
async () => {
|
|
const fixture = await makeFixture()
|
|
const homePath = join(fixture.root, 'home')
|
|
const resourcesPath = await createPackagedMacLauncher(fixture.root)
|
|
const usrLocalBin = join(fixture.root, 'usr', 'local', 'bin')
|
|
const userLocalBin = join(homePath, '.local', 'bin')
|
|
const defaultInstallPath = join(usrLocalBin, 'orca')
|
|
const userInstallPath = join(userLocalBin, 'orca')
|
|
const launcherPath = join(resourcesPath, 'bin', 'orca')
|
|
await mkdir(usrLocalBin, { recursive: true })
|
|
await mkdir(userLocalBin, { recursive: true })
|
|
await symlink(launcherPath, defaultInstallPath)
|
|
await symlink(launcherPath, userInstallPath)
|
|
|
|
const installer = new CliInstaller({
|
|
platform: 'darwin',
|
|
isPackaged: true,
|
|
resourcesPath,
|
|
userDataPath: fixture.userDataPath,
|
|
execPath: '/Applications/Orca.app/Contents/MacOS/Orca',
|
|
appPath: fixture.appPath,
|
|
homePath,
|
|
defaultMacCommandPath: defaultInstallPath,
|
|
processPathEnv: `${usrLocalBin}:${userLocalBin}`
|
|
})
|
|
|
|
const status = await installer.getStatus()
|
|
expect(status.commandPath).toBe(defaultInstallPath)
|
|
expect(status.state).toBe('installed')
|
|
}
|
|
)
|
|
|
|
// Why: shells skip missing PATH entries, so a managed command later in PATH
|
|
// is still the shell-visible Orca command until the default path is installed.
|
|
it.skipIf(process.platform === 'win32')(
|
|
'uses a later managed macOS orca command when the default command is missing',
|
|
async () => {
|
|
const fixture = await makeFixture()
|
|
const homePath = join(fixture.root, 'home')
|
|
const resourcesPath = await createPackagedMacLauncher(fixture.root)
|
|
const usrLocalBin = join(fixture.root, 'usr', 'local', 'bin')
|
|
const userLocalBin = join(homePath, '.local', 'bin')
|
|
const defaultInstallPath = join(usrLocalBin, 'orca')
|
|
const userInstallPath = join(userLocalBin, 'orca')
|
|
const launcherPath = join(resourcesPath, 'bin', 'orca')
|
|
await mkdir(usrLocalBin, { recursive: true })
|
|
await mkdir(userLocalBin, { recursive: true })
|
|
await symlink(launcherPath, userInstallPath)
|
|
|
|
const installer = new CliInstaller({
|
|
platform: 'darwin',
|
|
isPackaged: true,
|
|
resourcesPath,
|
|
userDataPath: fixture.userDataPath,
|
|
execPath: '/Applications/Orca.app/Contents/MacOS/Orca',
|
|
appPath: fixture.appPath,
|
|
homePath,
|
|
defaultMacCommandPath: defaultInstallPath,
|
|
processPathEnv: `${usrLocalBin}:${userLocalBin}`
|
|
})
|
|
|
|
const status = await installer.getStatus()
|
|
expect(status.commandPath).toBe(userInstallPath)
|
|
expect(status.state).toBe('installed')
|
|
|
|
const installed = await installer.install()
|
|
expect(installed.commandPath).toBe(userInstallPath)
|
|
await expect(lstat(defaultInstallPath)).rejects.toMatchObject({ code: 'ENOENT' })
|
|
}
|
|
)
|
|
|
|
// Why: bash/zsh skip non-executable PATH entries even at Orca's configured
|
|
// install slot, then keep looking for a runnable command later in PATH.
|
|
it.skipIf(process.platform === 'win32')(
|
|
'uses a later managed macOS orca command when the default command is not executable',
|
|
async () => {
|
|
const fixture = await makeFixture()
|
|
const homePath = join(fixture.root, 'home')
|
|
const resourcesPath = await createPackagedMacLauncher(fixture.root)
|
|
const usrLocalBin = join(fixture.root, 'usr', 'local', 'bin')
|
|
const userLocalBin = join(homePath, '.local', 'bin')
|
|
const defaultInstallPath = join(usrLocalBin, 'orca')
|
|
const userInstallPath = join(userLocalBin, 'orca')
|
|
const launcherPath = join(resourcesPath, 'bin', 'orca')
|
|
await mkdir(usrLocalBin, { recursive: true })
|
|
await mkdir(userLocalBin, { recursive: true })
|
|
await writeFile(defaultInstallPath, '#!/usr/bin/env bash\necho other-orca\n', 'utf8')
|
|
await symlink(launcherPath, userInstallPath)
|
|
|
|
const installer = new CliInstaller({
|
|
platform: 'darwin',
|
|
isPackaged: true,
|
|
resourcesPath,
|
|
userDataPath: fixture.userDataPath,
|
|
execPath: '/Applications/Orca.app/Contents/MacOS/Orca',
|
|
appPath: fixture.appPath,
|
|
homePath,
|
|
defaultMacCommandPath: defaultInstallPath,
|
|
processPathEnv: `${usrLocalBin}:${userLocalBin}`
|
|
})
|
|
|
|
const status = await installer.getStatus()
|
|
expect(status.commandPath).toBe(userInstallPath)
|
|
expect(status.state).toBe('installed')
|
|
|
|
const installed = await installer.install()
|
|
expect(installed.commandPath).toBe(userInstallPath)
|
|
await expect(readFile(defaultInstallPath, 'utf8')).resolves.toContain('other-orca')
|
|
}
|
|
)
|
|
|
|
// Why: a non-Orca command after an empty default install slot can be shadowed
|
|
// by installing the default path without replacing the user's command.
|
|
it.skipIf(process.platform === 'win32')(
|
|
'installs the default macOS command instead of replacing an unmanaged later command',
|
|
async () => {
|
|
const fixture = await makeFixture()
|
|
const homePath = join(fixture.root, 'home')
|
|
const resourcesPath = await createPackagedMacLauncher(fixture.root)
|
|
const usrLocalBin = join(fixture.root, 'usr', 'local', 'bin')
|
|
const userLocalBin = join(homePath, '.local', 'bin')
|
|
const defaultInstallPath = join(usrLocalBin, 'orca')
|
|
const userInstallPath = join(userLocalBin, 'orca')
|
|
const launcherPath = join(resourcesPath, 'bin', 'orca')
|
|
await mkdir(usrLocalBin, { recursive: true })
|
|
await mkdir(userLocalBin, { recursive: true })
|
|
await writeFile(userInstallPath, '#!/usr/bin/env bash\necho other-orca\n', {
|
|
encoding: 'utf8',
|
|
mode: 0o755
|
|
})
|
|
|
|
const installer = new CliInstaller({
|
|
platform: 'darwin',
|
|
isPackaged: true,
|
|
resourcesPath,
|
|
userDataPath: fixture.userDataPath,
|
|
execPath: '/Applications/Orca.app/Contents/MacOS/Orca',
|
|
appPath: fixture.appPath,
|
|
homePath,
|
|
defaultMacCommandPath: defaultInstallPath,
|
|
processPathEnv: `${usrLocalBin}:${userLocalBin}`
|
|
})
|
|
|
|
const status = await installer.getStatus()
|
|
expect(status.commandPath).toBe(defaultInstallPath)
|
|
expect(status.state).toBe('not_installed')
|
|
|
|
const installed = await installer.install()
|
|
expect(installed.commandPath).toBe(defaultInstallPath)
|
|
expect(installed.state).toBe('installed')
|
|
await expect(readlink(defaultInstallPath)).resolves.toBe(launcherPath)
|
|
await expect(readFile(userInstallPath, 'utf8')).resolves.toContain('other-orca')
|
|
}
|
|
)
|
|
|
|
// Why: an off-PATH ~/.local/bin/orca must not hijack CLI registration and
|
|
// leave the shell-visible /usr/local/bin command missing.
|
|
it.skipIf(process.platform === 'win32')(
|
|
'ignores managed macOS orca commands that are not visible on the shell PATH',
|
|
async () => {
|
|
const fixture = await makeFixture()
|
|
const homePath = join(fixture.root, 'home')
|
|
const resourcesPath = await createPackagedMacLauncher(fixture.root)
|
|
const usrLocalBin = join(fixture.root, 'usr', 'local', 'bin')
|
|
const userLocalBin = join(homePath, '.local', 'bin')
|
|
const defaultInstallPath = join(usrLocalBin, 'orca')
|
|
const userInstallPath = join(userLocalBin, 'orca')
|
|
const launcherPath = join(resourcesPath, 'bin', 'orca')
|
|
await mkdir(usrLocalBin, { recursive: true })
|
|
await mkdir(userLocalBin, { recursive: true })
|
|
await symlink(launcherPath, userInstallPath)
|
|
|
|
const installer = new CliInstaller({
|
|
platform: 'darwin',
|
|
isPackaged: true,
|
|
resourcesPath,
|
|
userDataPath: fixture.userDataPath,
|
|
execPath: '/Applications/Orca.app/Contents/MacOS/Orca',
|
|
appPath: fixture.appPath,
|
|
homePath,
|
|
defaultMacCommandPath: defaultInstallPath,
|
|
processPathEnv: usrLocalBin
|
|
})
|
|
|
|
const status = await installer.getStatus()
|
|
expect(status.commandPath).toBe(defaultInstallPath)
|
|
expect(status.pathConfigured).toBe(true)
|
|
expect(status.state).toBe('not_installed')
|
|
|
|
const installed = await installer.install()
|
|
expect(installed.commandPath).toBe(defaultInstallPath)
|
|
expect(installed.state).toBe('installed')
|
|
await expect(readlink(defaultInstallPath)).resolves.toBe(launcherPath)
|
|
await expect(readlink(userInstallPath)).resolves.toBe(launcherPath)
|
|
}
|
|
)
|
|
|
|
it.skipIf(process.platform === 'win32')(
|
|
'reports a conflict for an unmanaged macOS orca that shadows the install path',
|
|
async () => {
|
|
const fixture = await makeFixture()
|
|
const homePath = join(fixture.root, 'home')
|
|
const resourcesPath = await createPackagedMacLauncher(fixture.root)
|
|
const usrLocalBin = join(fixture.root, 'usr', 'local', 'bin')
|
|
const userLocalBin = join(homePath, '.local', 'bin')
|
|
const defaultInstallPath = join(usrLocalBin, 'orca')
|
|
const userInstallPath = join(userLocalBin, 'orca')
|
|
await mkdir(usrLocalBin, { recursive: true })
|
|
await mkdir(userLocalBin, { recursive: true })
|
|
await writeFile(userInstallPath, '#!/usr/bin/env bash\necho other-orca\n', {
|
|
encoding: 'utf8',
|
|
mode: 0o755
|
|
})
|
|
|
|
const installer = new CliInstaller({
|
|
platform: 'darwin',
|
|
isPackaged: true,
|
|
resourcesPath,
|
|
userDataPath: fixture.userDataPath,
|
|
execPath: '/Applications/Orca.app/Contents/MacOS/Orca',
|
|
appPath: fixture.appPath,
|
|
homePath,
|
|
defaultMacCommandPath: defaultInstallPath,
|
|
processPathEnv: `${userLocalBin}:${usrLocalBin}`
|
|
})
|
|
|
|
const status = await installer.getStatus()
|
|
expect(status.commandPath).toBe(userInstallPath)
|
|
expect(status.state).toBe('conflict')
|
|
await expect(installer.install()).rejects.toThrow('Refusing to replace non-Orca command')
|
|
await expect(lstat(defaultInstallPath)).rejects.toMatchObject({ code: 'ENOENT' })
|
|
await expect(readFile(userInstallPath, 'utf8')).resolves.toContain('other-orca')
|
|
}
|
|
)
|
|
|
|
// Why: bash/zsh skip non-executable PATH entries, so reporting them as a
|
|
// conflict would block a valid later install path the shell would use.
|
|
it.skipIf(process.platform === 'win32')(
|
|
'skips a non-executable unmanaged macOS orca before the install path',
|
|
async () => {
|
|
const fixture = await makeFixture()
|
|
const homePath = join(fixture.root, 'home')
|
|
const resourcesPath = await createPackagedMacLauncher(fixture.root)
|
|
const usrLocalBin = join(fixture.root, 'usr', 'local', 'bin')
|
|
const userLocalBin = join(homePath, '.local', 'bin')
|
|
const defaultInstallPath = join(usrLocalBin, 'orca')
|
|
const userInstallPath = join(userLocalBin, 'orca')
|
|
const launcherPath = join(resourcesPath, 'bin', 'orca')
|
|
await mkdir(usrLocalBin, { recursive: true })
|
|
await mkdir(userLocalBin, { recursive: true })
|
|
await writeFile(userInstallPath, '#!/usr/bin/env bash\necho other-orca\n', 'utf8')
|
|
|
|
const installer = new CliInstaller({
|
|
platform: 'darwin',
|
|
isPackaged: true,
|
|
resourcesPath,
|
|
userDataPath: fixture.userDataPath,
|
|
execPath: '/Applications/Orca.app/Contents/MacOS/Orca',
|
|
appPath: fixture.appPath,
|
|
homePath,
|
|
defaultMacCommandPath: defaultInstallPath,
|
|
processPathEnv: `${userLocalBin}:${usrLocalBin}`
|
|
})
|
|
|
|
const status = await installer.getStatus()
|
|
expect(status.commandPath).toBe(defaultInstallPath)
|
|
expect(status.state).toBe('not_installed')
|
|
|
|
const installed = await installer.install()
|
|
expect(installed.commandPath).toBe(defaultInstallPath)
|
|
expect(installed.state).toBe('installed')
|
|
await expect(readlink(defaultInstallPath)).resolves.toBe(launcherPath)
|
|
await expect(readFile(userInstallPath, 'utf8')).resolves.toContain('other-orca')
|
|
}
|
|
)
|
|
|
|
// Why: when macCommandPath falls back to ~/.local/bin/orca on arm64, commandName
|
|
// must still be 'orca' (not 'orca-ide' which is Linux-only).
|
|
it.skipIf(process.platform === 'win32')(
|
|
'reports commandName as orca (not orca-ide) when falling back to ~/.local/bin on macOS',
|
|
async () => {
|
|
const fixture = await makeFixture()
|
|
const homePath = join(fixture.root, 'home')
|
|
const resourcesPath = await createPackagedMacLauncher(fixture.root)
|
|
const absentUsrLocalBin = join(fixture.root, 'usr', 'local', 'bin', 'orca')
|
|
const installer = new CliInstaller({
|
|
platform: 'darwin',
|
|
isPackaged: true,
|
|
resourcesPath,
|
|
userDataPath: fixture.userDataPath,
|
|
execPath: '/Applications/Orca.app/Contents/MacOS/Orca',
|
|
appPath: fixture.appPath,
|
|
homePath,
|
|
defaultMacCommandPath: absentUsrLocalBin,
|
|
processPathEnv: join(homePath, '.local', 'bin')
|
|
})
|
|
|
|
const status = await installer.getStatus()
|
|
expect(status.commandName).toBe('orca')
|
|
}
|
|
)
|
|
|
|
// Why: macCommandPath is resolved at construction — getStatus() must return the
|
|
// same commandPath on repeated calls without re-running existsSync.
|
|
it.skipIf(process.platform === 'win32')(
|
|
'resolves macCommandPath once at construction — commandPath stable across repeated getStatus()',
|
|
async () => {
|
|
const fixture = await makeFixture()
|
|
const homePath = join(fixture.root, 'home')
|
|
const resourcesPath = await createPackagedMacLauncher(fixture.root)
|
|
const absentUsrLocalBin = join(fixture.root, 'usr', 'local', 'bin', 'orca')
|
|
const installer = new CliInstaller({
|
|
platform: 'darwin',
|
|
isPackaged: true,
|
|
resourcesPath,
|
|
userDataPath: fixture.userDataPath,
|
|
execPath: '/Applications/Orca.app/Contents/MacOS/Orca',
|
|
appPath: fixture.appPath,
|
|
homePath,
|
|
defaultMacCommandPath: absentUsrLocalBin,
|
|
processPathEnv: join(homePath, '.local', 'bin')
|
|
})
|
|
|
|
const s1 = await installer.getStatus()
|
|
await mkdir(dirname(absentUsrLocalBin), { recursive: true })
|
|
const s2 = await installer.getStatus()
|
|
const s3 = await installer.getStatus()
|
|
|
|
expect(s1.commandPath).toBe(s2.commandPath)
|
|
expect(s2.commandPath).toBe(s3.commandPath)
|
|
expect(s1.commandPath).toBe(join(homePath, '.local', 'bin', 'orca'))
|
|
}
|
|
)
|
|
|
|
// Why: the arm64 fallback must apply for packaged builds, not just dev launchers.
|
|
it.skipIf(process.platform === 'win32')(
|
|
'resolves to ~/.local/bin/orca on arm64 even when isPackaged is true',
|
|
async () => {
|
|
const fixture = await makeFixture()
|
|
const homePath = join(fixture.root, 'home')
|
|
const absentUsrLocalBin = join(fixture.root, 'usr', 'local', 'bin', 'orca')
|
|
const resourcesPath = join(fixture.root, 'resources')
|
|
const bundledLauncher = join(resourcesPath, 'bin', 'orca')
|
|
await mkdir(join(resourcesPath, 'bin'), { recursive: true })
|
|
await writeFile(bundledLauncher, '#!/usr/bin/env bash\necho orca\n', {
|
|
encoding: 'utf8',
|
|
mode: 0o755
|
|
})
|
|
|
|
const installer = new CliInstaller({
|
|
platform: 'darwin',
|
|
isPackaged: true,
|
|
resourcesPath,
|
|
userDataPath: fixture.userDataPath,
|
|
execPath: '/Applications/Orca.app/Contents/MacOS/Orca',
|
|
appPath: fixture.appPath,
|
|
homePath,
|
|
defaultMacCommandPath: absentUsrLocalBin,
|
|
processPathEnv: join(homePath, '.local', 'bin')
|
|
})
|
|
|
|
const status = await installer.getStatus()
|
|
expect(status.commandPath).toBe(join(homePath, '.local', 'bin', 'orca'))
|
|
expect(status.supported).toBe(true)
|
|
}
|
|
)
|
|
})
|