mirror of
https://github.com/stablyai/orca.git
synced 2026-10-09 08:02:35 +00:00
* test(ssh): prove connect-time conversion to managed orcad on real Linux and Windows hosts, and the downgrade view * fix(e2e): read the SSH host's session partition, provision the convert cell's account, and keep rollout file overrides out of packaged builds * test(e2e): report the full relay state when the convert poll times out * test(e2e): require the relay to list no terminals before the converting connect * test(e2e): require no running-terminal lease before the converting connect * test(e2e): report the host's terminal leases when the converting connect keeps the relay * test(e2e): end the relay era with no relay shell left to respawn * test(e2e): settle before the converting connect and name the tabs a blocking shell belongs to * test(e2e): use the exited relay tab as the session tab, since any mounted tab starts a shell * test(e2e): carry an editor tab through the conversion instead of a terminal tab * test(e2e): log the conversion census inputs before the converting connect * fix(orcad): log which state blocked a refused conversion * test(e2e): log both session partitions before the converting connect * fix(orcad): a source partition's copy of focus on another host no longer blocks conversion * fix(ssh): an ssh2 forward on port 0 reports the port it bound, so managed tunnels pair * test(e2e): give the conversion its full budget again * test(e2e): report the migration journal phase when the conversion stalls * test(e2e): report the connect's own result and main's state when the conversion stalls * fix(ssh): a converted host's managed state reaches the renderer instead of staying on 'converting' * test(e2e): print a failed server call's response * test(e2e): give server calls the budget a fresh server's first inventory needs * test(e2e): read the converted worktree's tabs with a scoped session.tabs.list * test(e2e): log the converted worktree's tabs instead of asserting them, pending the server-side fix * test(e2e): prove retirement by the dropped source rows; the journal compacts away after it * test(e2e): drop the conversion diagnostics now the cell passes * test(e2e): fail a hung disconnect or connect with main's state instead of the whole budget * test(e2e): convert an upgraded relay-era profile's host on its first connect, on Docker and Windows * test(e2e): seed the relay-era target the way addTarget registers it * fix(ssh): a stale ssh2 forward drops a late connection instead of crashing main on 'Not connected' * fix(orcad): the active-slot readiness probe reads Windows hosts through the host script * ci(ssh-windows): let only the convert cell's account open the SSH local forward its managed server needs * test(e2e): match server paths in their JSON-escaped form, for Windows backslashes --------- Co-authored-by: m4air <m4air@m4airs-Air.localdomain> Co-authored-by: m4air <m4air@Mac.localdomain>
206 lines
8.9 KiB
JavaScript
206 lines
8.9 KiB
JavaScript
import { readFileSync } from 'node:fs'
|
|
import { join, resolve } from 'node:path'
|
|
import { describe, expect, it } from 'vitest'
|
|
import { parse } from 'yaml'
|
|
import {
|
|
WINDOWS_FORBIDDEN_TOOLS,
|
|
WINDOWS_HOST_CELL_IDS,
|
|
WINDOWS_CONVERT_CELL_ID,
|
|
WINDOWS_ORCAD_CELL_IDS
|
|
} from '../../src/main/ssh/ssh-windows-host-cells.ts'
|
|
|
|
const projectDir = resolve(import.meta.dirname, '../..')
|
|
const workflow = parse(
|
|
readFileSync(join(projectDir, '.github/workflows/ssh-windows-hosts.yml'), 'utf8')
|
|
)
|
|
const job = workflow.jobs.hosts
|
|
const runStep = job.steps.find((step) => step.name?.startsWith('Run the Windows host cells'))
|
|
const provisioning = readFileSync(
|
|
join(projectDir, 'config/ci/windows-ssh-provider/preview-ssh/prove-preview-openssh.ps1'),
|
|
'utf8'
|
|
)
|
|
const capability = readFileSync(
|
|
join(projectDir, 'config/ci/windows-ssh-provider/preview-ssh/windows-ssh-capability.ps1'),
|
|
'utf8'
|
|
)
|
|
const manifest = (arch) =>
|
|
JSON.parse(
|
|
readFileSync(
|
|
join(
|
|
projectDir,
|
|
`config/ci/windows-ssh-provider/preview-ssh/preview-native-inputs-${arch}.json`
|
|
),
|
|
'utf8'
|
|
)
|
|
)
|
|
|
|
describe('SSH Windows-host workflow', () => {
|
|
it('runs on demand and on path-filtered, non-draft pull requests only', () => {
|
|
expect(Object.keys(workflow.on).sort()).toEqual(['pull_request', 'workflow_dispatch'])
|
|
const paths = workflow.on.pull_request.paths
|
|
expect(paths).toContain('src/main/ssh/ssh-relay-*')
|
|
expect(paths).toContain('config/ci/windows-ssh-provider/**')
|
|
expect(paths.indexOf('src/main/ssh/ssh-relay-windows-host-lane.test.ts')).toBeGreaterThan(
|
|
paths.indexOf('!src/**/*.test.ts')
|
|
)
|
|
expect(job.if).toContain('github.event.pull_request.draft != true')
|
|
})
|
|
|
|
it('covers inbox and preview OpenSSH on both Windows architectures', () => {
|
|
const cells = job.strategy.matrix.include.map(({ arch, runner, server }) =>
|
|
[arch, runner, server].join('/')
|
|
)
|
|
expect(cells.sort()).toEqual([
|
|
'arm64/windows-11-arm/inbox',
|
|
'arm64/windows-11-arm/preview',
|
|
'x64/windows-2022/inbox',
|
|
'x64/windows-2022/preview'
|
|
])
|
|
expect(job.env).toMatchObject({ ORCA_BACKGROUND_LAUNCH: '1', ORCA_ISOLATED_SSH_CI: '1' })
|
|
expect(job.strategy['fail-fast']).toBe(false)
|
|
expect(job['timeout-minutes']).toBe(75)
|
|
expect(runStep['timeout-minutes']).toBe(50)
|
|
})
|
|
|
|
it('overlaps only guarded ARM inbox capability preparation with the existing builds', () => {
|
|
const selfTestIndex = job.steps.findIndex((step) => step.name?.startsWith('Self-test'))
|
|
const prepareIndex = job.steps.findIndex((step) => step.id === 'inbox-capability')
|
|
const installIndex = job.steps.findIndex(
|
|
(step) => step.uses === './.github/actions/install-node-dependencies'
|
|
)
|
|
const buildIndex = job.steps.findIndex((step) => step.name?.startsWith('Build this runner'))
|
|
const prebuildIndex = job.steps.findIndex(
|
|
(step) => step.uses === './.github/actions/prepare-orcad-prebuilds'
|
|
)
|
|
const templateIndex = job.steps.findIndex((step) => step.name?.startsWith('Build the win32'))
|
|
const waitIndex = job.steps.findIndex((step) => step.wait === 'inbox-capability')
|
|
const runIndex = job.steps.indexOf(runStep)
|
|
expect([
|
|
selfTestIndex,
|
|
prepareIndex,
|
|
installIndex,
|
|
buildIndex,
|
|
prebuildIndex,
|
|
templateIndex,
|
|
waitIndex,
|
|
runIndex
|
|
]).toEqual([1, 2, 3, 4, 5, 6, 7, 8])
|
|
expect(job.steps[prepareIndex]).toMatchObject({
|
|
background: true,
|
|
shell: 'pwsh'
|
|
})
|
|
expect(job.steps[prepareIndex].if).toBeUndefined()
|
|
expect(job.steps[waitIndex].if).toBeUndefined()
|
|
expect(job.steps[prepareIndex].run.trim()).toMatch(
|
|
/^if\('\$\{\{ matrix\.server }}' -eq 'inbox' -and '\$\{\{ matrix\.arch }}' -eq 'arm64'\)\{[\s\S]+\}$/
|
|
)
|
|
expect(job.steps[prepareIndex].run).toContain('Initialize-WindowsInboxSshCapability')
|
|
expect(job.steps[prepareIndex].run).toContain('inbox-capability-preparation.json')
|
|
expect(runStep.run).toContain('-InboxPreparationReceipt $preparation')
|
|
expect(runStep.run).toContain(
|
|
"$preparation=Join-Path $receipts 'inbox-capability-preparation.json'"
|
|
)
|
|
expect(runStep.run).toContain(
|
|
"if('${{ matrix.server }}' -eq 'inbox' -and '${{ matrix.arch }}' -eq 'arm64'){$preparation="
|
|
)
|
|
expect(runStep.background).toBeUndefined()
|
|
expect(job.steps.at(-1)).toMatchObject({ if: 'always()', uses: 'actions/upload-artifact@v7' })
|
|
})
|
|
|
|
it('shares one capability installer without bypassing native verification or private cleanup', () => {
|
|
expect(capability.match(/Add-WindowsCapability -Online/g)).toHaveLength(1)
|
|
expect(provisioning).not.toContain('Add-WindowsCapability')
|
|
expect(provisioning).toContain(". (Join-Path $PSScriptRoot 'windows-ssh-capability.ps1')")
|
|
expect(provisioning).toContain('Install-WindowsInboxSshCapability $Arch $report')
|
|
const install = capability.slice(
|
|
capability.indexOf('function Install-WindowsInboxSshCapability'),
|
|
capability.indexOf('function Initialize-WindowsInboxSshCapability')
|
|
)
|
|
const mutation = install.indexOf('Add-WindowsCapability')
|
|
expect(install.indexOf('Assert-IsolatedWindowsSshCi')).toBeLessThan(mutation)
|
|
expect(install.indexOf('Assert-WindowsSshGlobalServerDormant')).toBeLessThan(mutation)
|
|
expect(install.lastIndexOf('Assert-WindowsSshGlobalServerDormant')).toBeGreaterThan(mutation)
|
|
expect(install.indexOf('Assert-WindowsSshStockShell')).toBeLessThan(mutation)
|
|
expect(install.lastIndexOf('Assert-WindowsSshStockShell')).toBeGreaterThan(mutation)
|
|
for (const check of [
|
|
'(Machine $path) -ne $target.machine',
|
|
'Get-AuthenticodeSignature -LiteralPath $path',
|
|
'Inbox native input Microsoft signature invalid',
|
|
"Write-Stage 'host-cell-probe-start'",
|
|
"Write-Stage 'cleanup-default-shell-start'",
|
|
"Write-Stage 'cleanup-service-stop-delete-start'"
|
|
]) {
|
|
expect(provisioning).toContain(check)
|
|
}
|
|
})
|
|
|
|
it('keeps preparation provenance separate from the oracle current capability state', () => {
|
|
for (const [field, environment] of [
|
|
['sourceSha', 'GITHUB_SHA'],
|
|
['runId', 'GITHUB_RUN_ID'],
|
|
['runAttempt', 'GITHUB_RUN_ATTEMPT'],
|
|
['runnerName', 'RUNNER_NAME'],
|
|
['imageVersion', 'ImageVersion']
|
|
]) {
|
|
expect(capability).toContain(`${field}=$env:${environment}`)
|
|
expect(provisioning).toContain(`$preparation.${field} -ne $env:${environment}`)
|
|
}
|
|
expect(provisioning).toContain("$preparation.status -ne 'passed'")
|
|
expect(provisioning).toContain('$preparation.arch -ne $Arch')
|
|
expect(provisioning).toContain('$report.inboxCapabilityPreparation=$preparation')
|
|
expect(capability).toContain('$Report.inboxCapabilityInitialState=[string]$capability.State')
|
|
expect(capability).toContain("$report.status='failed';$report.error=$_.Exception.Message;throw")
|
|
})
|
|
|
|
it('fetches the preview release its hash manifests pin', () => {
|
|
for (const arch of ['x64', 'arm64']) {
|
|
expect(runStep.run).toContain(
|
|
`https://github.com/PowerShell/Win32-OpenSSH/releases/download/${manifest(arch).release}/`
|
|
)
|
|
}
|
|
})
|
|
|
|
it('builds the template for this runner only and shims exactly the cell toolchain list', () => {
|
|
const build = job.steps.map((step) => step.run ?? '').join('\n')
|
|
expect(build).toContain('--targets "win32-${{ matrix.arch }}"')
|
|
expect(build).toContain('pnpm run build:relay')
|
|
const shimmed = /-HiddenTools @\(([^)]*)\)/.exec(runStep.run)?.[1]
|
|
expect(shimmed?.split(',').map((tool) => tool.trim().replaceAll("'", ''))).toEqual([
|
|
...WINDOWS_FORBIDDEN_TOOLS
|
|
])
|
|
})
|
|
|
|
it('defaults to every cell the TypeScript lane knows', () => {
|
|
const defaults = /\{\$cells=@\(([^)]*)\)\}/.exec(runStep.run)?.[1]
|
|
expect(defaults?.split(',').map((id) => id.trim().replaceAll("'", ''))).toEqual([
|
|
...WINDOWS_HOST_CELL_IDS,
|
|
...WINDOWS_ORCAD_CELL_IDS
|
|
])
|
|
const invoker = readFileSync(
|
|
join(projectDir, 'config/ci/windows-ssh-provider/invoke-pinned-relay-cells.ps1'),
|
|
'utf8'
|
|
)
|
|
for (const id of [...WINDOWS_HOST_CELL_IDS, ...WINDOWS_ORCAD_CELL_IDS]) {
|
|
expect(invoker).toContain(`'${id}'`)
|
|
}
|
|
expect(invoker).toContain('src/main/ssh/orcad-windows-host-lane.test.ts')
|
|
})
|
|
|
|
it('provisions one private account for every cell, convert cell included', () => {
|
|
expect(runStep.run).toContain(`$cells+='${WINDOWS_CONVERT_CELL_ID}'`)
|
|
// Only the convert cell reaches a managed server, through an SSH local forward.
|
|
expect(runStep.run).toContain(
|
|
`$forwarding=if($cells[-1] -eq '${WINDOWS_CONVERT_CELL_ID}'){1}else{0}`
|
|
)
|
|
expect(runStep.run).toContain('-ForwardingAccounts $forwarding')
|
|
const provisioner = readFileSync(
|
|
join(projectDir, 'config/ci/windows-ssh-provider/preview-ssh/prove-preview-openssh.ps1'),
|
|
'utf8'
|
|
)
|
|
const max = Number(/\[ValidateRange\(1,(\d+)\)\]\[int\]\$Accounts/.exec(provisioner)?.[1])
|
|
expect(max).toBeGreaterThanOrEqual(
|
|
WINDOWS_HOST_CELL_IDS.length + WINDOWS_ORCAD_CELL_IDS.length + 1
|
|
)
|
|
})
|
|
})
|