test: remove mock-only suites and native text-entry harness

This commit is contained in:
OrcaWin
2026-11-13 20:32:28 -08:00
parent d0882ca38c
commit 3db779d412
9 changed files with 4 additions and 807 deletions
+2 -4
View File
@@ -5365,7 +5365,7 @@
"pnpm exec vitest run --config config/vitest.config.ts src/shared/remote-runtime/remote-runtime-client.test.ts src/main/ipc/runtime-environments/browser-hosting/runtime-environment-browser-client-host-handler.test.ts",
"SKIP_BUILD=1 ORCA_CROSS_VERSION_PACKAGED_EXECUTABLE=/Applications/Orca.app/Contents/MacOS/Orca pnpm exec playwright test tests/e2e/packaged-mixed-version-browser-placement.spec.ts --config tests/playwright.config.ts --project electron-headless --workers=1",
"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/browser/network/browser-network-tunnel-paired-runtime.integration.test.ts src/main/browser/network/execution-routes/paired-runtime-browser-network-route.test.ts src/main/browser/network/execution-routes/browser-network-execution-route.test.ts src/main/browser/network/execution-routes/wsl-browser-network-execution-route.test.ts src/main/browser/network/execution-routes/wsl-browser-network-relay-launch.test.ts src/main/runtime/browser/network/runtime-browser-network-execution-host.test.ts",
"pnpm exec vitest run --config config/vitest.config.ts src/main/browser/cdp/screencast/browser-screencast-stream.test.ts src/main/runtime/browser/screencast/orca-runtime-browser-screencast-fanout.test.ts",
"pnpm exec vitest run --config config/vitest.config.ts src/main/browser/cdp/screencast/browser-screencast-stream.test.ts",
"ORCA_RUN_DOCKER_SSH_BROWSER_E2E=1 pnpm exec vitest run --config config/vitest.config.ts tests/e2e/ssh-browser-network-execution-route.docker.unit.test.ts",
"Manual iOS 26.5 simulator: pair current mobile code to packaged Orca 1.4.184; create Browser; navigate to https://example.com; require one visible Example Domain tab on the server-hosted surface",
"ORCA_RUN_DOCKER_SSH_BROWSER_E2E=1 node_modules/.bin/vitest run --config config/vitest.config.ts tests/e2e/ssh-browser-network-execution-route.docker.unit.test.ts"
@@ -5384,7 +5384,6 @@
"src/main/browser/network/execution-routes/wsl-browser-network-relay-launch.test.ts",
"src/main/runtime/browser/network/runtime-browser-network-execution-host.test.ts",
"src/main/browser/cdp/screencast/browser-screencast-stream.test.ts",
"src/main/runtime/browser/screencast/orca-runtime-browser-screencast-fanout.test.ts",
"tests/e2e/ssh-browser-network-execution-route.docker.unit.test.ts"
],
"assertionRefs": [
@@ -6169,7 +6168,7 @@
"commands": [
"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/terminal/surface/orca-runtime-terminal-close-continuity.test.ts",
"pnpm exec vitest run --config config/vitest.config.ts src/main/runtime/construction/orca-runtime.test.ts",
"pnpm dlx node@24 ./node_modules/vitest/vitest.mjs run --config config/vitest.config.ts src/main/agent-hooks/server/testing/authority/server-pane-authority.test.ts src/main/ipc/agent-hooks.test.ts src/main/ipc/agent-pane-authority-ownership.test.ts src/main/persistence/restoring-sessions/persistence-pane-identity-migration.test.ts src/main/persistence/loading-store/terminal-bindings/persistence-pty-binding-reconciliation.test.ts src/renderer/src/store/slices/agent-status/agent-pane-authority.test.ts src/renderer/src/store/terminals/layout/terminal-pane-detach-agent-identity.test.ts src/renderer/src/store/terminals/retirement/terminal-tab-retirement-store.test.ts src/renderer/src/components/shared/kill-all-terminal-surfaces.test.ts",
"pnpm dlx node@24 ./node_modules/vitest/vitest.mjs run --config config/vitest.config.ts src/main/agent-hooks/server/testing/authority/server-pane-authority.test.ts src/main/ipc/agent-pane-authority-ownership.test.ts src/main/persistence/restoring-sessions/persistence-pane-identity-migration.test.ts src/main/persistence/loading-store/terminal-bindings/persistence-pty-binding-reconciliation.test.ts src/renderer/src/store/slices/agent-status/agent-pane-authority.test.ts src/renderer/src/store/terminals/layout/terminal-pane-detach-agent-identity.test.ts src/renderer/src/store/terminals/retirement/terminal-tab-retirement-store.test.ts src/renderer/src/components/shared/kill-all-terminal-surfaces.test.ts",
"pnpm exec vitest run --config config/vitest.config.ts src/main/pty/process-retirement/pty-descendant-termination.test.ts src/main/daemon/terminal-host/testing/terminal-host.test.ts src/main/providers/local-pty/local-pty-provider-shutdown.test.ts src/main/runtime/workspaces/removal/worktree-teardown.test.ts",
"pnpm run test:e2e -- tests/e2e/terminal-tab-close-restart-persistence.spec.ts --workers=1",
"pnpm run test:e2e -- tests/e2e/terminal-parked-close-retirement.spec.ts --workers=1",
@@ -6181,7 +6180,6 @@
"src/main/runtime/terminal/surface/orca-runtime-terminal-close-continuity.test.ts",
"src/main/runtime/construction/orca-runtime.test.ts",
"src/main/agent-hooks/server/testing/authority/server-pane-authority.test.ts",
"src/main/ipc/agent-hooks.test.ts",
"src/main/ipc/agent-pane-authority-ownership.test.ts",
"src/main/persistence/restoring-sessions/persistence-pane-identity-migration.test.ts",
"src/main/persistence/loading-store/terminal-bindings/persistence-pty-binding-reconciliation.test.ts",
@@ -75,19 +75,6 @@ const checks = [
],
enabled: process.platform === 'win32'
},
{
name: 'Windows text entry tests',
command: 'powershell.exe',
args: [
'-NoLogo',
'-NoProfile',
'-ExecutionPolicy',
'Bypass',
'-File',
'native/computer-use-windows/runtime-text-entry.test.ps1'
],
enabled: process.platform === 'win32'
},
{
name: 'Windows native control tests',
command: 'powershell.exe',
@@ -1,189 +0,0 @@
param([string]$RuntimePath = (Join-Path $PSScriptRoot "runtime.ps1"))
$ErrorActionPreference = "Stop"
function Assert-TestEqual($Actual, $Expected, [string]$Label) {
if ($Actual -cne $Expected) { throw "$Label expected '$Expected', received '$Actual'" }
}
Add-Type -TypeDefinition @"
using System;
using System.Collections.Generic;
public static class OrcaDesktopWin32 {
public static readonly List<string> Calls = new List<string>();
public static IntPtr Foreground;
public static string EditText;
public static int[] Selection;
public static int EditOwner;
public static bool IgnoreInsertion;
public static long Limit;
public static void Reset(string text, int start, int end) {
Calls.Clear();
Foreground = new IntPtr(202);
EditText = text;
Selection = new[] { start, end };
EditOwner = 4242;
IgnoreInsertion = false;
Limit = 30000;
}
public static int GetProcessIntegrityLevel(int processId) { return 0x2000; }
public static IntPtr GetForegroundWindow() { return Foreground; }
public static bool SetForegroundWindow(IntPtr hwnd) { Calls.Add("activate"); return false; }
public static bool IsIconic(IntPtr hwnd) { return false; }
public static bool ShowWindow(IntPtr hwnd, int command) { Calls.Add("restore"); return true; }
public static bool IsWindow(IntPtr hwnd) { return true; }
public static int GetWindowLong(IntPtr hwnd, int index) { return 0x0004; }
public static uint GetWindowThreadProcessId(IntPtr hwnd, out uint processId) {
processId = hwnd == new IntPtr(303) ? (uint)EditOwner : (hwnd == new IntPtr(101) ? 4242u : 1u);
return 1;
}
public static string GetEditText(IntPtr hwnd, int maxLength) { return EditText; }
public static int[] GetEditSelection(IntPtr hwnd) { return Selection; }
public static bool ReplaceEditSelection(IntPtr hwnd, string text) {
Calls.Add("replace:" + text);
if (!IgnoreInsertion) {
EditText = EditText.Substring(0, Selection[0]) + text + EditText.Substring(Selection[1]);
}
return true;
}
public static bool SendUnicodeText(string text) { Calls.Add("keys:" + text); return true; }
public static long? SendControlMessage(IntPtr hwnd, uint message, IntPtr wParam, IntPtr lParam) { return message == 0x00D5 ? Limit : 0; }
}
"@
$tokens = $null
$errors = $null
$ast = [System.Management.Automation.Language.Parser]::ParseFile($RuntimePath, [ref]$tokens, [ref]$errors)
if ($errors.Count) { throw ($errors | Out-String) }
$functions = @(
"Test-OrcaWindowFocused", "Wait-OrcaWindowFocused", "Assert-OrcaKeyboardFocus", "New-OrcaForegroundRefusal",
"Assert-OrcaWindowTarget", "Get-OrcaRequiredString", "Invoke-OrcaOperation",
"Assert-OrcaInputAllowed", "Test-OrcaTargetTookForeground", "Get-OrcaProperty", "Test-OrcaSensitiveElement",
"Test-OrcaEditClass", "Get-OrcaNativeEdit", "Assert-OrcaWindowOwner", "Test-OrcaNativeClass", "ConvertTo-OrcaEditNewlines",
"Invoke-OrcaEditTextInsertion", "Invoke-OrcaElementTextInsertion", "Send-OrcaText",
"Test-OrcaElementDisabled", "Assert-OrcaStillFocused"
)
foreach ($definition in $ast.FindAll({ param($node) $node -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $false)) {
if ($definition.Name -in $functions) { . ([scriptblock]::Create($definition.Extent.Text)) }
}
$EditReadbackLimit = 1048576
function Find-OrcaProcess { [pscustomobject]@{ MainWindowHandle = 101; Id = 4242; ProcessName = "Fixture" } }
function Get-OrcaAppName($Process) { $Process.ProcessName }
function Get-OrcaRootElement { $null }
function Get-OrcaWindowFrame { @{ x = 100; y = 200 } }
function Find-OrcaElement($RootElement, $Record) { if ($null -eq $Record) { $null } else { $Record.live } }
function Invoke-OrcaValueTextInsertion { $script:valueWrites++; $null }
function Send-OrcaKey { [OrcaDesktopWin32]::Calls.Add("sendkeys") }
function Send-OrcaHotkey { [OrcaDesktopWin32]::Calls.Add("sendkeys") }
function Send-OrcaPasteText { [OrcaDesktopWin32]::Calls.Add("sendkeys") }
function New-OrcaSnapshot { [pscustomobject]@{} }
function Test-OrcaTextPatternReadOnly { $false }
function Restore-OrcaWindow { [OrcaDesktopWin32]::Calls.Add("activate"); $null }
function New-TestElement([string]$ClassName, [int64]$Handle, [bool]$Enabled = $true, [string]$ControlType = "ControlType.Document") {
$live = [pscustomobject]@{
Current = [pscustomobject]@{
ClassName = $ClassName; NativeWindowHandle = $Handle; IsPassword = $false; IsEnabled = $Enabled; Name = "Body"
AutomationId = ""; LocalizedControlType = "document"; ControlType = [pscustomobject]@{ ProgrammaticName = $ControlType }
}
}
[pscustomobject]@{ index = 7; live = $live }
}
function Invoke-TestOperation($Operation) {
$message = $null
$result = $null
try { $result = Invoke-OrcaOperation $Operation } catch { $message = $_.Exception.Message }
[pscustomobject]@{ result = $result; message = $message }
}
# Background keyboard verbs refuse before any key leaves, and say how to enter text without the foreground.
foreach ($operation in @(
[pscustomobject]@{ tool = "type_text"; text = "hello" },
[pscustomobject]@{ tool = "paste_text"; text = "hello" },
[pscustomobject]@{ tool = "press_key"; key = "Return" },
[pscustomobject]@{ tool = "hotkey"; key = "Ctrl+A" }
)) {
[OrcaDesktopWin32]::Reset("", 0, 0)
$operation | Add-Member -NotePropertyName app -NotePropertyValue "Fixture"
$outcome = Invoke-TestOperation $operation
if ($outcome.message -notlike "background_unavailable: *takes the user's keyboard; nothing was sent.*type-text --element-index*Ask the user before retrying with --restore-window.") {
throw "$($operation.tool) background refusal, received '$($outcome.message)'"
}
Assert-TestEqual ([OrcaDesktopWin32]::Calls.Count) 0 "$($operation.tool) background refusal sends nothing"
}
# Typing into a native edit replaces its selection in the background and reads the result back.
[OrcaDesktopWin32]::Reset("one`r`nthree", 3, 3)
$outcome = Invoke-TestOperation ([pscustomobject]@{ tool = "type_text"; app = "Fixture"; text = "`ntwo"; element = (New-TestElement "Edit" 303) })
if ($null -ne $outcome.message) { throw "edit insertion failed: $($outcome.message)" }
Assert-TestEqual ([OrcaDesktopWin32]::Calls -join "|") "replace:`r`ntwo" "edit insertion sends one EM_REPLACESEL with CRLF line breaks"
Assert-TestEqual ([OrcaDesktopWin32]::EditText) "one`r`ntwo`r`nthree" "edit insertion result"
Assert-TestEqual $outcome.result.action.actionName "EM_REPLACESEL" "edit insertion action"
Assert-TestEqual $outcome.result.action.delivery "background" "edit insertion delivery"
Assert-TestEqual $outcome.result.action.verification.state "verified" "edit insertion verification"
# A field that ignores the insertion is an error, not a typed result.
[OrcaDesktopWin32]::Reset("same", 4, 4)
[OrcaDesktopWin32]::IgnoreInsertion = $true
$outcome = Invoke-TestOperation ([pscustomobject]@{ tool = "type_text"; app = "Fixture"; text = "x"; element = (New-TestElement "Edit" 303) })
if ($outcome.message -notlike "text_insertion_unconfirmed:*") { throw "ignored insertion, received '$($outcome.message)'" }
# Replacing a selection with the same text changes nothing yet is the requested result.
[OrcaDesktopWin32]::Reset("abc", 0, 3)
$outcome = Invoke-TestOperation ([pscustomobject]@{ tool = "type_text"; app = "Fixture"; text = "abc"; element = (New-TestElement "Edit" 303) })
if ($null -ne $outcome.message) { throw "identical replacement failed: $($outcome.message)" }
Assert-TestEqual $outcome.result.action.verification.state "verified" "identical replacement verification"
# An edit cuts text past its limit, so an insertion that would overflow it is refused before anything is sent.
[OrcaDesktopWin32]::Reset("old", 3, 3)
[OrcaDesktopWin32]::Limit = 5
$outcome = Invoke-TestOperation ([pscustomobject]@{ tool = "type_text"; app = "Fixture"; text = "abc"; element = (New-TestElement "Edit" 303) })
if ($outcome.message -notlike "element 7 text is not settable: the result would be 6 characters, past the field's 5-character limit") { throw "over-limit insertion, received '$($outcome.message)'" }
Assert-TestEqual ([OrcaDesktopWin32]::EditText) "old" "over-limit insertion leaves the field untouched"
# A password field can't be read back, yet its limit still holds.
[OrcaDesktopWin32]::Reset("", 0, 0)
[OrcaDesktopWin32]::Limit = 2
$secret = New-TestElement "Edit" 303
$secret.live.Current.IsPassword = $true
$outcome = Invoke-TestOperation ([pscustomobject]@{ tool = "type_text"; app = "Fixture"; text = "abc"; element = $secret })
if ($outcome.message -notlike "*past the field's 2-character limit") { throw "over-limit password insertion, received '$($outcome.message)'" }
Assert-TestEqual ([OrcaDesktopWin32]::Calls.Count) 0 "over-limit password insertion sends nothing"
# Edit messages would bypass a disabled field's lock, so a disabled field is refused untouched.
[OrcaDesktopWin32]::Reset("", 0, 0)
$outcome = Invoke-TestOperation ([pscustomobject]@{ tool = "type_text"; app = "Fixture"; text = "x"; element = (New-TestElement "Edit" 303 $false) })
if ($outcome.message -notlike "element 7 text is not settable: the field is disabled") { throw "disabled edit, received '$($outcome.message)'" }
Assert-TestEqual ([OrcaDesktopWin32]::Calls.Count) 0 "disabled edit sends nothing"
# A recycled window handle owned by another process is never messaged.
[OrcaDesktopWin32]::Reset("", 0, 0)
[OrcaDesktopWin32]::EditOwner = 999
$outcome = Invoke-TestOperation ([pscustomobject]@{ tool = "type_text"; app = "Fixture"; text = "x"; element = (New-TestElement "Edit" 303) })
if ($outcome.message -notlike "stale element:*") { throw "recycled edit handle, received '$($outcome.message)'" }
Assert-TestEqual ([OrcaDesktopWin32]::Calls.Count) 0 "recycled edit handle sends nothing"
# An element with no background text route refuses instead of typing at whatever has focus.
[OrcaDesktopWin32]::Reset("", 0, 0)
$outcome = Invoke-TestOperation ([pscustomobject]@{ tool = "type_text"; app = "Fixture"; text = "x"; element = (New-TestElement "Chrome_RenderWidgetHostHWND" 404) })
if ($outcome.message -notlike "background_unavailable: element 7 accepts text only as keys*") { throw "no text route, received '$($outcome.message)'" }
Assert-TestEqual ([OrcaDesktopWin32]::Calls.Count) 0 "no text route sends nothing"
# List and tree items take ValuePattern too, where a write would rename them, so they are refused untouched.
[OrcaDesktopWin32]::Reset("", 0, 0)
$script:valueWrites = 0
$outcome = Invoke-TestOperation ([pscustomobject]@{ tool = "type_text"; app = "Fixture"; text = "x"; element = (New-TestElement "" 0 $true "ControlType.ListItem") })
if ($outcome.message -notlike "element 7 text is not settable: it is not a text field") { throw "list item, received '$($outcome.message)'" }
Assert-TestEqual ([OrcaDesktopWin32]::Calls.Count + $script:valueWrites) 0 "list item sends nothing"
# Foreground typing re-checks focus right before SendInput, so a user's switch never receives the text.
[OrcaDesktopWin32]::Reset("", 0, 0)
[OrcaDesktopWin32]::Foreground = [IntPtr]101
$outcome = Invoke-TestOperation ([pscustomobject]@{ tool = "type_text"; app = "Fixture"; text = "caf$([char]0x00E9)"; restoreWindow = $true })
if ($null -ne $outcome.message) { throw "foreground typing failed: $($outcome.message)" }
Assert-TestEqual (@([OrcaDesktopWin32]::Calls | Where-Object { $_ -like "keys:*" }) -join "|") "keys:caf$([char]0x00E9)" "foreground typing sends Unicode input"
Write-Output "windows-text-entry-tests-ok"
-155
View File
@@ -1,155 +0,0 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type * as AgentHookServerModule from '../agent-hooks/server/agent-hook-server'
import { makePaneKey } from '../../shared/workspace-layout/stable-pane-id'
// Renderer dismissals must evict persisted status as well as the live row.
const dropStatusEntry = vi.fn()
const dropPersistedStatusEntry = vi.fn()
const dropPersistedStatusEntries = vi.fn(() => [] as string[])
const dropStatusEntriesByTabPrefix = vi.fn()
const retirePaneAuthority = vi.fn()
const transferPaneAuthority = vi.fn()
const canTransferPaneAuthority = vi.fn(() => true)
const getStatusSnapshot = vi.fn()
const inferInterrupt = vi.fn()
const clearMigrationUnsupportedPtysByTabPrefix = vi.fn()
const clearMigrationUnsupportedPtysForPaneKey = vi.fn()
const onHandlers = new Map<string, (event: unknown, ...args: unknown[]) => void>()
const handleHandlers = new Map<string, (event: unknown, ...args: unknown[]) => unknown>()
const removeHandler = vi.fn()
const removeAllListeners = vi.fn()
const PANE_KEY = makePaneKey('tab-1', '11111111-1111-4111-8111-111111111111')
const CHILD_PANE_KEY = makePaneKey('tab-2', '22222222-2222-4222-8222-222222222222')
vi.mock('electron', () => ({
ipcMain: {
handle: (channel: string, handler: (event: unknown, ...args: unknown[]) => unknown) => {
handleHandlers.set(channel, handler)
},
on: (channel: string, handler: (event: unknown, ...args: unknown[]) => void) => {
onHandlers.set(channel, handler)
},
removeHandler,
removeAllListeners
}
}))
vi.mock('../agent-hooks/server/agent-hook-server', async () => {
// Use the real pane-key validator so invalid-input coverage tracks its constraints.
const actual = await vi.importActual<typeof AgentHookServerModule>(
'../agent-hooks/server/agent-hook-server'
)
return {
...actual,
agentHookServer: {
dropStatusEntry,
dropPersistedStatusEntry,
dropPersistedStatusEntries,
dropStatusEntriesByTabPrefix,
retirePaneAuthority,
transferPaneAuthority,
canTransferPaneAuthority,
getStatusSnapshot,
inferInterrupt
}
}
})
vi.mock('../agent-hooks/observations/migration-unsupported-pty-state', () => ({
clearMigrationUnsupportedPtysByTabPrefix,
clearMigrationUnsupportedPtysForPaneKey,
getMigrationUnsupportedPtySnapshot: vi.fn(() => [])
}))
vi.mock('../claude/hook-service', () => ({
claudeHookService: { getStatus: vi.fn(() => ({ agent: 'claude', state: 'absent' })) }
}))
vi.mock('../openclaude/hook-service', () => ({
openClaudeHookService: { getStatus: vi.fn(() => ({ agent: 'openclaude', state: 'absent' })) }
}))
vi.mock('../codex/hooks/installation/hook-service', () => ({
codexHookService: { getStatus: vi.fn(() => ({ agent: 'codex', state: 'absent' })) }
}))
vi.mock('../gemini/hook-service', () => ({
geminiHookService: { getStatus: vi.fn(() => ({ agent: 'gemini', state: 'absent' })) }
}))
vi.mock('../antigravity/hook-service', () => ({
antigravityHookService: { getStatus: vi.fn(() => ({ agent: 'antigravity', state: 'absent' })) }
}))
vi.mock('../amp/hook-service', () => ({
ampHookService: { getStatus: vi.fn(() => ({ agent: 'amp', state: 'absent' })) }
}))
vi.mock('../cursor/hook-service', () => ({
cursorHookService: { getStatus: vi.fn(() => ({ agent: 'cursor', state: 'absent' })) }
}))
vi.mock('../droid/hook-service', () => ({
droidHookService: { getStatus: vi.fn(() => ({ agent: 'droid', state: 'absent' })) }
}))
vi.mock('../command-code/hook-service', () => ({
commandCodeHookService: { getStatus: vi.fn(() => ({ agent: 'command-code', state: 'absent' })) }
}))
vi.mock('../grok/hook-service', () => ({
grokHookService: { getStatus: vi.fn(() => ({ agent: 'grok', state: 'absent' })) }
}))
vi.mock('../copilot/hook-service', () => ({
copilotHookService: { getStatus: vi.fn(() => ({ agent: 'copilot', state: 'absent' })) }
}))
vi.mock('../hermes/hook-service', () => ({
hermesHookService: { getStatus: vi.fn(() => ({ agent: 'hermes', state: 'absent' })) }
}))
vi.mock('../devin/hook-service', () => ({
devinHookService: { getStatus: vi.fn(() => ({ agent: 'devin', state: 'absent' })) }
}))
vi.mock('../kimi/hook-service', () => ({
kimiHookService: { getStatus: vi.fn(() => ({ agent: 'kimi', state: 'absent' })) }
}))
beforeEach(() => {
dropStatusEntry.mockReset()
dropPersistedStatusEntry.mockReset()
dropPersistedStatusEntries.mockReset()
dropPersistedStatusEntries.mockReturnValue([])
dropStatusEntriesByTabPrefix.mockReset()
retirePaneAuthority.mockReset()
transferPaneAuthority.mockReset()
canTransferPaneAuthority.mockReset()
canTransferPaneAuthority.mockReturnValue(true)
getStatusSnapshot.mockReset()
inferInterrupt.mockReset()
clearMigrationUnsupportedPtysByTabPrefix.mockReset()
clearMigrationUnsupportedPtysForPaneKey.mockReset()
onHandlers.clear()
handleHandlers.clear()
removeHandler.mockReset()
removeAllListeners.mockReset()
})
afterEach(() => {
vi.resetModules()
})
describe('agent pane authority IPC', () => {
it('rejects unowned and oversized pane authority transfers', async () => {
const { registerAgentHookHandlers } = await import('./agent-hooks')
registerAgentHookHandlers()
const transfer = onHandlers.get('agentStatus:transferPaneAuthority')!
canTransferPaneAuthority.mockReturnValue(false)
transfer({}, { fromPaneKey: PANE_KEY, toPaneKey: CHILD_PANE_KEY, ptyId: 'forged-pty' })
transfer({}, { fromPaneKey: PANE_KEY, toPaneKey: CHILD_PANE_KEY })
canTransferPaneAuthority.mockReturnValue(true)
transfer({}, { fromPaneKey: PANE_KEY, toPaneKey: CHILD_PANE_KEY, ptyId: 'x'.repeat(513) })
transfer(
{},
{
fromPaneKey: `${'x'.repeat(180)}:11111111-1111-4111-8111-111111111111`,
toPaneKey: CHILD_PANE_KEY,
ptyId: 'pty-1'
}
)
expect(transferPaneAuthority).not.toHaveBeenCalled()
})
})
-102
View File
@@ -1,102 +0,0 @@
import { EventEmitter } from 'node:events'
import { mkdir, mkdtemp, realpath, rm, symlink, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, beforeEach, expect, it, vi } from 'vitest'
import type { Store } from '../persistence/loading-store/store'
import type * as RepoWorktrees from '../workspaces/catalog/repo-worktrees'
import { invalidateAuthorizedRootsCache } from './worktrees/removal/registered-worktree-roots-cache'
type Handler = (event: unknown, args: unknown) => unknown
const { handlers, startNotebookKernelMock, userData } = vi.hoisted(() => ({
handlers: new Map<string, Handler>(),
startNotebookKernelMock: vi.fn(),
userData: { path: '' }
}))
vi.mock('electron', () => ({
app: { getPath: () => userData.path },
ipcMain: { handle: (channel: string, handler: Handler) => handlers.set(channel, handler) }
}))
vi.mock('../workspaces/catalog/repo-worktrees', async () => {
const actual = await vi.importActual<typeof RepoWorktrees>('../workspaces/catalog/repo-worktrees')
return { ...actual, listRepoWorktreeGraph: vi.fn(async () => []) }
})
vi.mock('../notebook/notebook-kernel', () => ({ startNotebookKernel: startNotebookKernelMock }))
import { registerNotebookHandlers } from './notebook'
let base: string
let project: string
function storeWithProject(projectPath: string): Store {
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: authorization reads only these Store members.
return {
getRepos: () => [
{ id: 'repo', path: projectPath, displayName: 'project', badgeColor: '#000', addedAt: 0 }
],
getProjects: () => [],
getProjectGroups: () => [],
getFolderWorkspaces: () => [],
getSettings: () => ({ nestWorkspaces: false, workspaceDir: '' })
} as unknown as Store
}
beforeEach(async () => {
invalidateAuthorizedRootsCache()
handlers.clear()
startNotebookKernelMock.mockReset().mockReturnValue({
kernel: { execute: vi.fn(), interrupt: vi.fn(), shutdown: vi.fn() },
ready: Promise.resolve({ status: 'ready' }),
exited: new Promise(() => {})
})
base = await mkdtemp(join(await realpath(tmpdir()), 'orca-notebook-link-'))
project = join(base, 'project')
userData.path = join(base, 'user-data')
await mkdir(join(project, 'analysis'), { recursive: true })
await mkdir(join(userData.path, 'floating-workspace'), { recursive: true })
await writeFile(join(project, 'analysis', 'real.ipynb'), '{}')
registerNotebookHandlers(storeWithProject(project))
})
afterEach(async () => {
await rm(base, { recursive: true, force: true })
})
it.skipIf(process.platform === 'win32')(
'runs a notebook opened through a project link in its real folder, as the project check resolves it',
async () => {
await symlink(join(project, 'analysis', 'real.ipynb'), join(project, 'nb.ipynb'))
const owner = Object.assign(new EventEmitter(), { send: vi.fn(), isDestroyed: () => false })
await handlers.get('notebook:startKernel')!(
{ sender: owner },
{ filePath: join(project, 'nb.ipynb'), python: '/py' }
)
expect(startNotebookKernelMock).toHaveBeenCalledWith(
expect.objectContaining({ cwd: join(project, 'analysis') })
)
}
)
it.skipIf(process.platform === 'win32')(
'runs a linked notebook outside every project in its real folder, as before',
async () => {
const notes = join(base, 'notes')
await mkdir(join(notes, 'analysis'), { recursive: true })
await writeFile(join(notes, 'analysis', 'real.ipynb'), '{}')
await symlink(join(notes, 'analysis', 'real.ipynb'), join(notes, 'nb.ipynb'))
const owner = Object.assign(new EventEmitter(), { send: vi.fn(), isDestroyed: () => false })
await handlers.get('notebook:startKernel')!(
{ sender: owner },
{ filePath: join(notes, 'nb.ipynb'), python: '/py' }
)
expect(startNotebookKernelMock).toHaveBeenCalledWith(
expect.objectContaining({ cwd: join(notes, 'analysis') })
)
}
)
@@ -1,136 +0,0 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { WebContents } from 'electron'
import type {
BrowserScreencastOptions,
BrowserScreencastSession
} from '../../../browser/cdp/screencast/browser-screencast-stream-types'
import {
createSinglePageBrowserCommandsHost,
createStreamedGuest
} from '../client-pages/single-page-browser-commands-host-test-double'
import { RuntimeBrowserCommands } from '../commands/browser-commands'
const { webContentsFromId, startBrowserScreencast } = vi.hoisted(() => ({
webContentsFromId: vi.fn(),
startBrowserScreencast:
vi.fn<
(guest: WebContents, options: BrowserScreencastOptions) => Promise<BrowserScreencastSession>
>()
}))
vi.mock('electron', () => ({
ipcMain: { on: vi.fn(), removeListener: vi.fn() },
webContents: { fromId: webContentsFromId }
}))
vi.mock('../../../browser/cdp/screencast/browser-screencast-stream', () => ({
startBrowserScreencast
}))
function createSession() {
let close!: () => void
const done = new Promise<void>((resolve) => {
close = resolve
})
return {
stop: vi.fn(() => close()),
done,
updateViewport: async () => {},
updateFrameBudget: async () => {}
}
}
const params = { page: 'page-1', format: 'jpeg' } as const
describe('screencast ownership', () => {
beforeEach(() => {
webContentsFromId.mockReset()
webContentsFromId.mockReturnValue(createStreamedGuest())
startBrowserScreencast.mockReset()
})
afterEach(() => {
vi.useRealTimers()
})
it.each(['older', 'newer'])(
'keeps the newer viewer when the %s reconnect finishes first',
async (firstReady) => {
const session = createSession()
let finishUpdate = (): void => {}
let updateStarted = (): void => {}
const started = new Promise<void>((resolve) => {
updateStarted = resolve
})
vi.spyOn(session, 'updateFrameBudget').mockImplementationOnce(
() =>
new Promise<void>((resolve) => {
finishUpdate = resolve
updateStarted()
})
)
startBrowserScreencast.mockResolvedValue(session)
const commands = new RuntimeBrowserCommands(createSinglePageBrowserCommandsHost())
const device = { pairedDeviceId: 'phone-1' }
const first = await commands.browserScreencast(params, { ...device, sendBinary: () => true })
const olderSend = vi.fn(() => true)
const newerSend = vi.fn(() => true)
const reconnectParams = { ...params, maxWidth: 640 }
const olderJoin = commands.browserScreencast(reconnectParams, {
...device,
sendBinary: olderSend
})
let finishViewport = (): void => {}
let viewportStarted = (): void => {}
const viewportPending = new Promise<void>((resolve) => {
viewportStarted = resolve
})
if (firstReady === 'older') {
vi.spyOn(session, 'updateViewport').mockImplementationOnce(
() =>
new Promise<void>((resolve) => {
finishViewport = resolve
viewportStarted()
})
)
}
try {
await started
const newerJoin = commands.browserScreencast(
{
...reconnectParams,
...(firstReady === 'older' ? { viewportWidth: 640, viewportHeight: 480 } : {})
},
{
...device,
sendBinary: newerSend
}
)
if (firstReady === 'older') {
await viewportPending
finishUpdate()
await olderJoin
finishViewport()
}
const newer = await newerJoin
await first.session.done
finishUpdate()
const older = await olderJoin
await older.session.done
const frame = new Uint8Array([1, 2, 3])
startBrowserScreencast.mock.calls[0]?.[1].onFrame(frame)
expect(olderSend).not.toHaveBeenCalled()
expect(newerSend).toHaveBeenCalledWith(frame, { dropWhenBacklogged: true })
expect(session.stop).not.toHaveBeenCalled()
newer.session.stop()
await newer.session.done
expect(session.stop).toHaveBeenCalledOnce()
} finally {
finishUpdate()
finishViewport()
session.stop()
await session.done
}
}
)
})
@@ -1,91 +0,0 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import {
createSinglePageBrowserCommandsHost,
createStreamedGuest
} from '../client-pages/single-page-browser-commands-host-test-double'
const { webContentsFromId, startBrowserScreencast } = vi.hoisted(() => ({
webContentsFromId: vi.fn(),
startBrowserScreencast: vi.fn()
}))
vi.mock('electron', () => ({
ipcMain: { on: vi.fn(), removeListener: vi.fn() },
webContents: { fromId: webContentsFromId }
}))
vi.mock('../../../browser/cdp/screencast/browser-screencast-stream', () => ({
startBrowserScreencast
}))
function deferred() {
let resolve!: () => void
const promise = new Promise<void>((done) => {
resolve = done
})
return { promise, resolve }
}
describe('RuntimeBrowserCommands screencast fanout', () => {
beforeEach(() => {
webContentsFromId.mockReset()
webContentsFromId.mockReturnValue(createStreamedGuest())
startBrowserScreencast.mockReset()
})
it('replays the joiner snapshot that its pre-ready gate refused', async () => {
const { RuntimeBrowserCommands } = await import('../commands/browser-commands')
const done = deferred()
const snapshot = new Uint8Array([9, 9, 9])
let onFrame: (bytes: Uint8Array<ArrayBufferLike>) => boolean | void = () => true
// updateViewport emits before the joining viewer's ready gate opens.
const updateViewport = vi.fn(async () => {
onFrame(snapshot)
})
startBrowserScreencast.mockImplementation(async (_guest: unknown, options: never) => {
onFrame = (options as { onFrame: typeof onFrame }).onFrame
return {
stop: vi.fn(() => done.resolve()),
done: done.promise,
updateViewport,
updateFrameBudget: vi.fn(async () => {})
}
})
const commands = new RuntimeBrowserCommands(createSinglePageBrowserCommandsHost())
const creatorSend = vi.fn(() => true)
await commands.browserScreencast(
{
worktree: 'id:wt-1',
page: 'page-1',
format: 'jpeg',
viewportWidth: 1200,
viewportHeight: 800
},
{ sendBinary: creatorSend }
)
let gateOpen = false
const joinerSend = vi.fn(() => gateOpen)
const joiner = await commands.browserScreencast(
{
worktree: 'id:wt-1',
page: 'page-1',
format: 'jpeg',
viewportWidth: 800,
viewportHeight: 600
},
{ sendBinary: joinerSend }
)
expect(creatorSend).toHaveBeenCalledWith(snapshot, { dropWhenBacklogged: true })
expect(joinerSend).toHaveBeenCalledWith(snapshot, { dropWhenBacklogged: true })
joinerSend.mockClear()
gateOpen = true
joiner.flushPendingFrame()
expect(joinerSend).toHaveBeenCalledExactlyOnceWith(snapshot, { dropWhenBacklogged: true })
joinerSend.mockClear()
joiner.flushPendingFrame()
expect(joinerSend).not.toHaveBeenCalled()
joiner.session.stop()
await joiner.session.done
})
})
@@ -64,26 +64,13 @@ function request(scope: BulkTabCloseScope) {
act(actions[scope])
}
async function confirmRequest(confirmed = true) {
async function confirmRequest() {
await act(async () => {
settle(confirmed)
settle(true)
})
}
describe('group bulk close confirmation', () => {
it.each(['editors', 'others', 'right', 'left'] as const)(
'waits for confirmation and leaves tabs untouched on cancelling %s',
async (scope) => {
request(scope)
expect(confirm).toHaveBeenCalledOnce()
expect(closeItem).not.toHaveBeenCalled()
expect(closeMany).not.toHaveBeenCalled()
await confirmRequest(false)
expect(closeItem).not.toHaveBeenCalled()
expect(closeMany).not.toHaveBeenCalled()
}
)
it.each(['others', 'right', 'left'] as const)(
'abandons %s when the anchor is removed while the dialog is open',
async (scope) => {
@@ -100,17 +87,6 @@ describe('group bulk close confirmation', () => {
}
)
it.each(['editors', 'others', 'right', 'left'] as const)(
'abandons %s when the original group disappears',
async (scope) => {
request(scope)
act(() => useAppStore.setState({ groupsByWorktree: {} }))
await confirmRequest()
expect(closeMany).not.toHaveBeenCalled()
expect(closeItem).not.toHaveBeenCalled()
}
)
it('skips editors pinned while confirmation is pending', async () => {
request('editors')
act(() =>
@@ -1,91 +0,0 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { SshConnectionState } from '../../../shared/ssh-types'
import type { Worktree } from '../../../shared/worktree/types'
import { createWebFileMutationMethods } from './web-file-mutation-methods'
function resolvedFile(
id: string,
hostId: Worktree['hostId'],
relativePath: string
): { worktree: Pick<Worktree, 'id' | 'hostId'>; relativePath: string } {
return { worktree: { id, hostId }, relativePath }
}
function connectedSshState(targetId: string, connectionGeneration: number): SshConnectionState {
return {
targetId,
status: 'connected',
error: null,
reconnectAttempt: 0,
connectionGeneration
}
}
describe('paired web file mutation methods', () => {
const assertMutationSupported = vi.fn(async (): Promise<readonly string[]> => [])
const callRuntimeResult = vi.fn(async () => ({ ok: true }))
const getSshState = vi.fn(async () => connectedSshState('hub-private-target', 17))
const filesByPath = new Map([
['/hub/repo/readme.md', resolvedFile('wt-local', 'local', 'readme.md')],
['/hub/repo/new.md', resolvedFile('wt-local', 'local', 'new.md')],
['/hub/repo/copy.md', resolvedFile('wt-local', 'local', 'copy.md')],
['/hub/repo/new-dir', resolvedFile('wt-local', 'local', 'new-dir')],
['/ssh/repo/source.md', resolvedFile('wt-ssh', 'ssh:hub-private-target', 'source.md')],
['/ssh/repo/renamed.md', resolvedFile('wt-ssh', 'ssh:hub-private-target', 'renamed.md')],
['/ssh/repo/copy.md', resolvedFile('wt-ssh', 'ssh:hub-private-target', 'copy.md')],
['/ssh/repo/dir', resolvedFile('wt-ssh', 'ssh:hub-private-target', 'dir')]
])
const resolveFilePath = vi.fn(async (filePath: string) => {
const file = filesByPath.get(filePath)
if (!file) {
throw new Error(`Unknown test path: ${filePath}`)
}
return file
})
const captureSession = vi.fn(() => ({
assertMutationSupported,
callRuntimeResult,
getSshState,
resolveFilePath
}))
beforeEach(() => {
assertMutationSupported.mockClear()
callRuntimeResult.mockClear()
getSshState.mockClear()
resolveFilePath.mockClear()
captureSession.mockClear()
})
it('keeps path resolution, capability, and mutation on one captured pairing session', async () => {
const replacementCall = vi.fn(async () => ({ ok: true }))
const replacementSession = {
assertMutationSupported: vi.fn(async (): Promise<readonly string[]> => []),
callRuntimeResult: replacementCall,
getSshState,
resolveFilePath
}
const capturedSession = {
assertMutationSupported: vi.fn(async (): Promise<readonly string[]> => {
captureSession.mockReturnValue(replacementSession)
return []
}),
callRuntimeResult,
getSshState,
resolveFilePath
}
captureSession.mockReturnValueOnce(capturedSession)
const methods = createWebFileMutationMethods({ captureSession })
await methods.writeFile({ filePath: '/hub/repo/readme.md', content: 'bound' })
expect(captureSession).toHaveBeenCalledTimes(1)
expect(callRuntimeResult).toHaveBeenCalledWith('files.write', {
worktree: 'id:wt-local',
relativePath: 'readme.md',
content: 'bound',
expectedExecutionHostId: 'local'
})
expect(replacementCall).not.toHaveBeenCalled()
})
})