mirror of
https://github.com/stablyai/orca.git
synced 2026-09-30 16:02:56 +00:00
125 lines
5.3 KiB
JavaScript
125 lines
5.3 KiB
JavaScript
import { readFileSync } from 'node:fs'
|
|
import { createRequire } from 'node:module'
|
|
import { dirname, join } from 'node:path'
|
|
import { describe, expect, it } from 'vitest'
|
|
import { runProcessSync } from '../../src/shared/child-process/run-process'
|
|
|
|
const hooks = readFileSync(new URL('../nsis/orca-installer-hooks.nsh', import.meta.url), 'utf8')
|
|
const processCheck = readFileSync(
|
|
new URL('../nsis/orca-process-check.nsh', import.meta.url),
|
|
'utf8'
|
|
)
|
|
const require = createRequire(import.meta.url)
|
|
const builderRequire = createRequire(require.resolve('electron-builder/package.json'))
|
|
const upstreamChecks = readFileSync(
|
|
join(
|
|
dirname(builderRequire.resolve('app-builder-lib/package.json')),
|
|
'templates/nsis/include/allowOnlyOneInstallerInstance.nsh'
|
|
),
|
|
'utf8'
|
|
)
|
|
|
|
function readPowerShellProbe(source = processCheck) {
|
|
const match = source.match(/nsExec::Exec `"\$PowerShellPath"(.*?) -(?:C|Command) "([^"\n]+)"`/)
|
|
if (!match) {
|
|
throw new Error('The NSIS PowerShell invocation was not found')
|
|
}
|
|
return { args: match[1].trim().split(/\s+/).filter(Boolean), command: match[2] }
|
|
}
|
|
|
|
describe('NSIS process-check integration', () => {
|
|
it('loads the capability hook through the installer and uninstaller include', () => {
|
|
expect(hooks).toContain('!include "${__FILEDIR__}\\orca-process-check.nsh"')
|
|
expect(processCheck).toMatch(/!macro customCheckAppRunning\b/)
|
|
expect(processCheck).toContain('!include "getProcessInfo.nsh"')
|
|
expect(processCheck).toMatch(/^Var pid$/m)
|
|
expect(processCheck).toMatch(/^Var \/GLOBAL IsPowerShellAvailable$/m)
|
|
})
|
|
|
|
it('keeps upstream process selection, retries, and installation-mode handling', () => {
|
|
expect(processCheck).toContain('!insertmacro _CHECK_APP_RUNNING')
|
|
expect(processCheck).not.toMatch(/!macro (?:FIND_PROCESS|KILL_PROCESS|_CHECK_APP_RUNNING)\b/)
|
|
expect(processCheck).not.toMatch(/\b(?:Stop-Process|taskkill|Set-ExecutionPolicy)\b/)
|
|
const findProcess = upstreamChecks.match(/!macro FIND_PROCESS\b[\s\S]*?!macroend/)?.[0]
|
|
if (!findProcess) {
|
|
throw new Error('The upstream process finder was not found')
|
|
}
|
|
expect(readPowerShellProbe().args).toEqual(readPowerShellProbe(findProcess).args)
|
|
})
|
|
})
|
|
|
|
describe.runIf(process.platform === 'win32')(
|
|
'NSIS capability probe under Restricted policy',
|
|
() => {
|
|
const policyReceipt = 'orca-nsis: restricted policy verified'
|
|
const queryFailureReceipt = 'orca-nsis: injected query failure'
|
|
const policyCheck = [
|
|
'function Test-OrcaRestrictedPolicy { param([string]$Scope)',
|
|
"try { $parameters = @{ ErrorAction = 'Stop' };",
|
|
'if ($Scope) { $parameters.Scope = $Scope };',
|
|
"return ((Get-ExecutionPolicy @parameters) -eq 'Restricted')",
|
|
'} catch { return $false } };',
|
|
// A failed getter must not fall through to the query's successful exit.
|
|
"if ((Test-OrcaRestrictedPolicy '__orca_invalid_scope__') -ne $false) { exit 11 };",
|
|
"if ((Test-OrcaRestrictedPolicy 'Process') -ne $true) { exit 10 };",
|
|
'if ((Test-OrcaRestrictedPolicy) -ne $true) { exit 10 };',
|
|
`[Console]::Out.WriteLine('${policyReceipt}');`
|
|
].join(' ')
|
|
|
|
// A watchdog against a wedged probe, not a latency budget: Windows PowerShell 5.1
|
|
// cold start under the packaged-CI process load has reached the former 20s cap. Matches
|
|
// the synchronous-probe ceiling in tests/tools/win-update-e2e/powershell-runner.mjs.
|
|
const PROBE_TIMEOUT_MS = 60_000
|
|
// Vitest's 30s default would otherwise cap the blocking spawn ahead of PROBE_TIMEOUT_MS.
|
|
const PROBE_TEST_TIMEOUT_MS = PROBE_TIMEOUT_MS + 15_000
|
|
|
|
function runProbe(arch, prefix = '') {
|
|
const { args, command } = readPowerShellProbe()
|
|
if (!process.env.SystemRoot) {
|
|
throw new Error('SystemRoot is required on Windows')
|
|
}
|
|
// The pwsh runner's module path points Windows PowerShell at incompatible PS7 modules.
|
|
const env = Object.fromEntries(
|
|
Object.entries(process.env).filter(([key]) => key.toLowerCase() !== 'psmodulepath')
|
|
)
|
|
return runProcessSync({
|
|
program: join(process.env.SystemRoot, arch, 'WindowsPowerShell', 'v1.0', 'powershell.exe'),
|
|
args: [...args, '-Command', `${policyCheck} ${prefix}${command}`],
|
|
env: {
|
|
...env,
|
|
ORCA_BACKGROUND_LAUNCH: '1',
|
|
PSExecutionPolicyPreference: 'Restricted'
|
|
},
|
|
timeoutMs: PROBE_TIMEOUT_MS
|
|
})
|
|
}
|
|
|
|
it.each(['SysWOW64', 'System32'])(
|
|
'%s permits the real inline process query',
|
|
{ timeout: PROBE_TEST_TIMEOUT_MS },
|
|
(arch) => {
|
|
const result = runProbe(arch)
|
|
expect(result.code, JSON.stringify(result)).toBe(0)
|
|
expect(result.timedOut).toBe(false)
|
|
expect(result.stdout).toContain(policyReceipt)
|
|
}
|
|
)
|
|
|
|
it.each(['SysWOW64', 'System32'])(
|
|
'%s rejects a failed process query',
|
|
{ timeout: PROBE_TEST_TIMEOUT_MS },
|
|
(arch) => {
|
|
const result = runProbe(
|
|
arch,
|
|
'function Get-CimInstance { [CmdletBinding()] param([string]$ClassName); ' +
|
|
`[Console]::Out.WriteLine('${queryFailureReceipt}'); Write-Error 'CIM unavailable' }; `
|
|
)
|
|
expect(result.code, JSON.stringify(result)).toBe(1)
|
|
expect(result.timedOut).toBe(false)
|
|
expect(result.stdout).toContain(policyReceipt)
|
|
expect(result.stdout).toContain(queryFailureReceipt)
|
|
}
|
|
)
|
|
}
|
|
)
|