From f01a97201b7b2278414d2041eabebff52e5a8d5c Mon Sep 17 00:00:00 2001 From: OrcaWin Date: Sun, 25 Oct 2026 20:09:25 -0700 Subject: [PATCH] test: remove wrapper replays and test fixture inventories (#33745) Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> --- config/flaky-test-pattern-baseline.txt | 7 +- .../mobile-native-chat-eligibility.test.ts | 5 - .../mobile-native-chat-pending-echo.test.ts | 22 -- ...ile-native-chat-pending-retirement.test.ts | 35 ---- .../compiled-operation-module.test.ts | 44 ---- .../declared-device-state.test.ts | 70 ------- .../rpc-recording/family-recordings.test.ts | 15 -- .../inventory-lifecycle-derivation.test.ts | 65 ------ .../rpc-recording/mounted-screen-tree.test.ts | 37 +--- .../mutants/family-mutants.test.ts | 9 +- .../rpc-recording/mutants/mutant-seam.test.ts | 82 -------- .../mutants/pilot-mutants.test.ts | 42 +--- .../mutants/probe-hole-witness.test.ts | 29 +-- .../native-mounting-substitutes.test.ts | 7 - ...peration-module-loader-work-budget.test.ts | 24 +-- .../rpc-recording-through-bridge.test.ts | 11 - .../screen-native-substitutes.test.ts | 43 +--- .../scripted-client-wrapper.test.ts | 9 - .../subscription-recording.test.ts | 52 ----- ...llular-handshake-stall-real-socket.test.ts | 111 ---------- mobile/src/transport/lifecycle-owner.test.ts | 197 ------------------ mobile/src/transport/workspace-server.test.ts | 61 ------ src/main/automations/dispatch-refusal.test.ts | 111 ---------- ...xternal-automation-probe-scheduler.test.ts | 55 +---- .../external-manager-scoped.test.ts | 61 ------ src/main/automations/external-manager.test.ts | 32 --- .../headless-automation-agent-launch.test.ts | 99 --------- .../headless-run-terminal-retention.test.ts | 23 -- .../headless-workspace-create.test.ts | 25 --- .../retained-run-reconciliation.test.ts | 44 +--- .../run-completion-watcher.test.ts | 19 -- .../automations/run-target-resolution.test.ts | 9 - ...ntime-automation-service-retention.test.ts | 18 -- .../runtime-automation-service.test.ts | 116 ----------- ...ime-terminal-run-observer-evidence.test.ts | 10 - .../runtime-terminal-run-observer.test.ts | 34 --- src/main/automations/service-precheck.test.ts | 50 ----- .../azure-devops-api-request.test.ts | 17 -- src/main/azure-devops/client.test.ts | 62 +----- .../pull-request-creation.test.ts | 10 +- .../azure-devops/pull-request-mappers.test.ts | 32 +-- src/main/azure-devops/repository-ref.test.ts | 13 -- src/main/bitbucket/client.test.ts | 69 ------ .../bitbucket/credential-connection.test.ts | 32 --- src/main/bitbucket/credential-store.test.ts | 25 --- .../bitbucket/pull-request-creation.test.ts | 23 +- .../bitbucket/pull-request-mappers.test.ts | 32 +-- src/main/bitbucket/repository-ref.test.ts | 13 -- src/main/browser-sidecar-owner.test.ts | 5 - ...-browser-bridge-capture-paint-hold.test.ts | 17 -- ...t-browser-bridge-command-transport.test.ts | 155 -------------- ...gent-browser-bridge-keypress-input.test.ts | 8 - .../agent-browser-bridge-mouse-input.test.ts | 13 -- .../agent-browser-bridge-navigation.test.ts | 59 ------ ...agent-browser-bridge-pointer-input.test.ts | 28 --- ...owser-bridge-raw-process-retention.test.ts | 45 ---- ...t-browser-bridge-screenshot-format.test.ts | 142 ------------- ...t-browser-bridge-session-lifecycle.test.ts | 103 --------- .../agent-browser-bridge-tab-routing.test.ts | 94 --------- .../agent-browser-bridge-text-input.test.ts | 31 --- .../agent-browser-orphan-sweep.test.ts | 44 ---- .../manifest-contract.test.ts | 30 --- ...stration-dispatch-refusal-contract.test.ts | 22 -- .../orchestration-fleet-attention.test.ts | 24 --- src/shared/orchestration-task-display.test.ts | 10 - .../osc-title-scan-tail-retention.test.ts | 44 +--- src/shared/osc52-clipboard.test.ts | 22 -- .../paired-e2ee-device-key-auth.test.ts | 17 +- .../pairing-address-auto-selection.test.ts | 11 - src/shared/pairing-local-ui-fields.test.ts | 25 +-- src/shared/pairing.test.ts | 11 - .../pane-agent-identity-adapter.test.ts | 21 -- .../pane-agent-identity-resolver.test.ts | 35 ---- src/shared/pi-state-title-marker.test.ts | 10 - .../plugin-consent-fingerprint.test.ts | 12 +- .../plugin-language-pack-artifact.test.ts | 44 ---- .../plugin-panel-message-budget.test.ts | 7 - src/shared/plugins/plugin-panel-shell.test.ts | 14 +- src/shared/pr-check-severity-order.test.ts | 24 --- src/shared/priority-semaphore.test.ts | 19 -- .../process-table-capture-parsing.test.ts | 41 ---- src/shared/process-table-index.test.ts | 19 -- src/shared/process-table-snapshot.test.ts | 52 +---- ...lated-workspace-state-builder.unit.test.ts | 49 ----- ...ternate-screen-fixture-script.unit.test.ts | 54 ----- 85 files changed, 21 insertions(+), 3351 deletions(-) delete mode 100644 mobile/src/test-support/rpc-recording/compiled-operation-module.test.ts delete mode 100644 mobile/src/test-support/rpc-recording/inventory-lifecycle-derivation.test.ts delete mode 100644 mobile/src/test-support/rpc-recording/mutants/mutant-seam.test.ts delete mode 100644 mobile/src/transport/cellular-handshake-stall-real-socket.test.ts delete mode 100644 mobile/src/transport/workspace-server.test.ts delete mode 100644 src/main/automations/dispatch-refusal.test.ts delete mode 100644 src/main/browser/agent-browser-bridge-screenshot-format.test.ts delete mode 100644 src/shared/process-table-capture-parsing.test.ts delete mode 100644 tests/e2e/accumulated-workspace-state-builder.unit.test.ts delete mode 100644 tests/e2e/alternate-screen-fixture-script.unit.test.ts diff --git a/config/flaky-test-pattern-baseline.txt b/config/flaky-test-pattern-baseline.txt index 2b1065db3e4..0d169f0b99c 100644 --- a/config/flaky-test-pattern-baseline.txt +++ b/config/flaky-test-pattern-baseline.txt @@ -3,7 +3,6 @@ # Prune after a fix: pnpm check:flaky-test-pattern-ratchet --prune 1 test-no-polling-loop:config/scripts/happy-dom-mutation-observer-retention.test.ts 1 test-no-polling-loop:config/scripts/mobile-web-app/mobile-web-app-frame-budget-sweep.test.ts -1 test-no-polling-loop:mobile/src/transport/cellular-handshake-stall-real-socket.test.ts 1 test-no-polling-loop:mobile/src/transport/rpc-client-live-recovery.test.ts 1 test-no-polling-loop:src/main/browser/cdp-ws-proxy.test.ts 1 test-no-polling-loop:src/main/browser/cookies/browser-cookie-import-concurrency.test.ts @@ -164,7 +163,6 @@ 1 test-no-real-time-wait:mobile/src/tasks/worktree-create-retry.test.ts 1 test-no-real-time-wait:mobile/src/terminal/terminal-web-document-mount.test.ts 1 test-no-real-time-wait:mobile/src/terminal/terminal-webview-tap-routing.test.ts -1 test-no-real-time-wait:mobile/src/transport/cellular-handshake-stall-real-socket.test.ts 1 test-no-real-time-wait:mobile/src/transport/mobile-relay-runtime-failover.test.ts 4 test-no-real-time-wait:mobile/src/transport/rpc-client-live-recovery.test.ts 1 test-no-real-time-wait:mobile/src/transport/rpc-operation-barrier.test.ts @@ -481,7 +479,6 @@ 5 test-no-real-time-wait:src/shared/fish-query-reply-child-stdin.node-pty.test.ts 1 test-no-real-time-wait:src/shared/git-branch-line-total-soft-deadline.test.ts 1 test-no-real-time-wait:src/shared/osc52-clipboard.test.ts -1 test-no-real-time-wait:src/shared/priority-semaphore.test.ts 4 test-no-real-time-wait:src/shared/pty-reply-echo-shapes.node-pty.test.ts 1 test-no-real-time-wait:src/shared/remote-runtime-outbound-admission.test.ts 2 test-no-real-time-wait:src/shared/remote-runtime/control/remote-runtime-shared-control-auth-refusal.test.ts @@ -704,7 +701,6 @@ 1 test-no-wall-clock:mobile/src/session/use-mobile-native-chat-message-send.test.ts 1 test-no-wall-clock:mobile/src/source-control/MobileGitHistoryList.test.tsx 1 test-no-wall-clock:mobile/src/tasks/worktree-create-launch-operation.test.ts -2 test-no-wall-clock:mobile/src/transport/cellular-handshake-stall-real-socket.test.ts 1 test-no-wall-clock:mobile/src/transport/host-store.test.ts 1 test-no-wall-clock:mobile/src/transport/mobile-relay-invite-director.test.ts 3 test-no-wall-clock:mobile/src/transport/mobile-relay-resume-director.test.ts @@ -1179,10 +1175,9 @@ 7 test-timeout-needs-reason:mobile/src/tasks/worktree-create-retry.test.ts 2 test-timeout-needs-reason:mobile/src/terminal/document/native-document-bundle.test.ts 1 test-timeout-needs-reason:mobile/src/test-support/rpc-recording/family-recordings.test.ts -2 test-timeout-needs-reason:mobile/src/test-support/rpc-recording/mutants/family-mutants.test.ts +1 test-timeout-needs-reason:mobile/src/test-support/rpc-recording/mutants/family-mutants.test.ts 1 test-timeout-needs-reason:mobile/src/test-support/rpc-recording/recorded-request-params.test.ts 1 test-timeout-needs-reason:mobile/src/test-support/rpc-recording/rpc-recording-through-bridge.test.ts -1 test-timeout-needs-reason:mobile/src/transport/cellular-handshake-stall-real-socket.test.ts 1 test-timeout-needs-reason:mobile/src/transport/mobile-web-bundle-host-slow-link.test.ts 1 test-timeout-needs-reason:mobile/src/transport/rpc-client-live-recovery.test.ts 3 test-timeout-needs-reason:mobile/src/transport/rpc-client-reconnect-wait.test.ts diff --git a/mobile/src/session/native-chat/mobile-native-chat-eligibility.test.ts b/mobile/src/session/native-chat/mobile-native-chat-eligibility.test.ts index 48bc4137408..55dc3ecde9d 100644 --- a/mobile/src/session/native-chat/mobile-native-chat-eligibility.test.ts +++ b/mobile/src/session/native-chat/mobile-native-chat-eligibility.test.ts @@ -193,11 +193,6 @@ describe('resolveMobileNativeChat', () => { }) ).toBeNull() }) - - it('canShowMobileNativeChat mirrors resolution', () => { - expect(canShowMobileNativeChat({ type: 'terminal', launchAgent: 'claude' })).toBe(true) - expect(canShowMobileNativeChat(null)).toBe(false) - }) }) it('resolves folder readability from the serving host catalog and rejects Model-A SSH', () => { diff --git a/mobile/src/session/native-chat/mobile-native-chat-pending-echo.test.ts b/mobile/src/session/native-chat/mobile-native-chat-pending-echo.test.ts index b74f8af188d..37af5c96337 100644 --- a/mobile/src/session/native-chat/mobile-native-chat-pending-echo.test.ts +++ b/mobile/src/session/native-chat/mobile-native-chat-pending-echo.test.ts @@ -80,19 +80,6 @@ describe('appendMobileNativeChatPending ordinals for repeated sends', () => { ) ).toEqual(['p2']) }) - - it('still counts a single-line repeat, which never regressed', () => { - const baseline = [userTurn('m1', 'unrelated')] - const both = appendMobileNativeChatPending( - appendMobileNativeChatPending({}, KEY, 'p1', sendOrigin('ping', baseline), 'ping'), - KEY, - 'p2', - sendOrigin('ping', baseline), - 'ping' - ) - - expect(both[KEY]?.map((item) => item.expectedOccurrence)).toEqual([1, 2]) - }) }) describe('mobile pending echoes whose text normalizes to nothing', () => { @@ -129,13 +116,4 @@ describe('mobile pending echoes whose text normalizes to nothing', () => { expect(both[KEY]?.map((item) => item.expectedOccurrence)).toEqual([1, 2]) }) - - it('records an image echo, whose text is empty by design', () => { - const pending = appendMobileNativeChatPending({}, KEY, 'p1', sendOrigin('', []), '', [ - 'file:///photo.jpg' - ]) - - expect(pending[KEY]).toHaveLength(1) - expect(pending[KEY]?.[0]?.expectedOccurrence).toBe(1) - }) }) diff --git a/mobile/src/session/native-chat/mobile-native-chat-pending-retirement.test.ts b/mobile/src/session/native-chat/mobile-native-chat-pending-retirement.test.ts index d3b6dd6555d..62b05310c2f 100644 --- a/mobile/src/session/native-chat/mobile-native-chat-pending-retirement.test.ts +++ b/mobile/src/session/native-chat/mobile-native-chat-pending-retirement.test.ts @@ -114,16 +114,6 @@ describe('selectGluedPendingIds', () => { expect(retiredIds(messages, pending)).toEqual([]) }) - it('retires three collapsed prompts in one row', () => { - const messages = [assistantTurn('m1', 'ready', 1000), userTurn('m2', 'one two three', 5000)] - const pending = [ - pendingSend('p1', 'one', 'm1'), - pendingSend('p2', 'two', 'm1'), - pendingSend('p3', 'three', 'm1') - ] - expect(retiredIds(messages, pending)).toEqual(['p1', 'p2', 'p3']) - }) - it('matches across tab, newline and repeated-space separators', () => { const messages = [ assistantTurn('m1', 'ready', 1000), @@ -136,18 +126,6 @@ describe('selectGluedPendingIds', () => { expect(retiredIds(messages, pending)).toEqual(['p1', 'p2']) }) - it('matches prompts that themselves contain the separator', () => { - const messages = [ - assistantTurn('m1', 'ready', 1000), - userTurn('m2', 'fix the bug and run the tests', 5000) - ] - const pending = [ - pendingSend('p1', 'fix the bug', 'm1'), - pendingSend('p2', 'and run the tests', 'm1') - ] - expect(retiredIds(messages, pending)).toEqual(['p1', 'p2']) - }) - it('matches when one pending is a strict prefix of the next', () => { const messages = [assistantTurn('m1', 'ready', 1000), userTurn('m2', 'run run the tests', 5000)] const pending = [pendingSend('p1', 'run', 'm1'), pendingSend('p2', 'run the tests', 'm1')] @@ -195,12 +173,6 @@ describe('selectGluedPendingIds', () => { expect(retiredIds(messages, pending)).toEqual([]) }) - it('never lets an older turn retire sends captured after it', () => { - const messages = [userTurn('m1', 'fix the bug', 1000), assistantTurn('m2', 'fixed', 1100)] - const pending = [pendingSend('p1', 'fix the', 'm2'), pendingSend('p2', 'bug', 'm2')] - expect(retiredIds(messages, pending)).toEqual([]) - }) - it('rejects a row the pending run only prefixes', () => { const messages = [ assistantTurn('m1', 'ready', 1000), @@ -403,11 +375,4 @@ describe('retireLandedMobileNativeChatPending on a PTY-typed send', () => { expect(retireLandedMobileNativeChatPending(messages, pending, NO_IMAGE_ECHOES)).toEqual([]) }) - - it('still holds the echo when no row carries the text at all', () => { - const messages = [assistantTurn('m1', 'ready', 1000)] - const pending = [pendingSend('p1', COMPOSER_TEXT, 'm1')] - - expect(retireLandedMobileNativeChatPending(messages, pending, NO_IMAGE_ECHOES)).toEqual(pending) - }) }) diff --git a/mobile/src/test-support/rpc-recording/compiled-operation-module.test.ts b/mobile/src/test-support/rpc-recording/compiled-operation-module.test.ts deleted file mode 100644 index a944904cf91..00000000000 --- a/mobile/src/test-support/rpc-recording/compiled-operation-module.test.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { compiledOperationModule } from './compiled-operation-module' - -describe('compiled operation code', () => { - it('reuses identical code but separates source, exposure and diagnostic filename', () => { - const source = 'const value: number = 1; export const read = () => value' - const original = compiledOperationModule('original.ts', source, '') - expect(compiledOperationModule('original.ts', source, '')).toBe(original) - expect(compiledOperationModule('original.ts', source.replace('= 1', '= 2'), '')).not.toBe( - original - ) - expect(compiledOperationModule('original.ts', source, '\nexports.extra = value')).not.toBe( - original - ) - expect(compiledOperationModule('different.ts', source, '')).not.toBe(original) - expect(compiledOperationModule('original.ts', source, '')).toBe(original) - }) - - it('bounds retained code and still evaluates an evicted module correctly', () => { - const source = 'export const value = 42' - const original = compiledOperationModule('evicted.ts', source, '') - for (let index = 0; index < 512; index++) { - compiledOperationModule(`bounded-${index}.ts`, 'export {}', '') - } - const recompiled = compiledOperationModule('evicted.ts', source, '') - expect(recompiled).not.toBe(original) - const exports = {} - recompiled(() => undefined, exports) - expect(exports).toEqual({ value: 42 }) - }) - - it('bounds retained variants of one file, not only the number of filenames', () => { - const source = 'export const value = 42' - const original = compiledOperationModule('variants.ts', source, '') - for (let index = 0; index < 512; index++) { - compiledOperationModule('variants.ts', `${source}; export const variant = ${index}`, '') - } - const recompiled = compiledOperationModule('variants.ts', source, '') - expect(recompiled).not.toBe(original) - const exports = {} - recompiled(() => undefined, exports) - expect(exports).toEqual({ value: 42 }) - }) -}) diff --git a/mobile/src/test-support/rpc-recording/declared-device-state.test.ts b/mobile/src/test-support/rpc-recording/declared-device-state.test.ts index 81dbdcd3698..94cc7df7c1a 100644 --- a/mobile/src/test-support/rpc-recording/declared-device-state.test.ts +++ b/mobile/src/test-support/rpc-recording/declared-device-state.test.ts @@ -1,7 +1,5 @@ import { resolve } from 'node:path' import { describe, expect, it } from 'vitest' -import { declaredDeviceSubstitutes } from './declared-device-state' -import { nativeMountingSubstitutes } from './native-mounting-substitutes' import { pilotMountAdapters } from './pilot-mount-adapters' import { runRecording } from './run-recording' import { readScenarios } from './scenario-input' @@ -15,23 +13,6 @@ const manifest = readScenarios( resolve(root, 'mobile/rpc-foundation/pilot-scenarios.json') ).scenarios -const STORE = '@react-native-async-storage/async-storage' - -function store(declared: Record): { - module: Record Promise> - effects: { name: string; value: unknown }[] -} { - const effects: { name: string; value: unknown }[] = [] - const device = declaredDeviceSubstitutes({ deviceStore: declared }) - device.bind((name, value) => effects.push({ name, value })) - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the declared store answers every member; the test asserts what each one does. - const module = device.substitutes.get(STORE) as Record< - string, - (...args: unknown[]) => Promise - > - return { module, effects } -} - function scenario(id: string): RecordingScenario { const found = manifest.find((candidate) => candidate.id === id) if (!found) { @@ -65,57 +46,6 @@ function sentParams(recording: Recording, index = 0): unknown { } describe('a scenario that declares its device', () => { - it('leaves the refusing store in place when nothing is declared', () => { - const undeclared = declaredDeviceSubstitutes({}) - expect(undeclared.substitutes.size).toBe(0) - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the table's default store answers every member with a throwing call. - const fallback = nativeMountingSubstitutes().get(STORE) as { getItem: () => unknown } - expect(() => fallback.getItem()).toThrow('Native store reached during recording') - }) - - it('reads the declared entry, and null for everything else', async () => { - const { module } = store({ 'orca:key': 'declared' }) - await expect(module.getItem!('orca:key')).resolves.toBe('declared') - await expect(module.getItem!('orca:other')).resolves.toBeNull() - }) - - it('records a write as an effect without letting a read see it', async () => { - const { module, effects } = store({}) - await module.setItem!('orca:key', 'written') - await module.removeItem!('orca:key') - await expect(module.getItem!('orca:key')).resolves.toBeNull() - expect(effects).toEqual([ - { name: 'device-store.setItem', value: { key: 'orca:key', value: 'written' } }, - { name: 'device-store.removeItem', value: { key: 'orca:key' } } - ]) - }) - - it('refuses a member the declaration does not back', () => { - const { module } = store({}) - expect(() => module.multiGet!('orca:key')).toThrow( - `Native store reached during recording: ${STORE}.multiGet` - ) - }) - - it('answers a declared tray, and records a dismissal as an effect', async () => { - const entry = { request: { identifier: 'tray-1', content: { data: { a: 1 } } } } - const effects: { name: string; value: unknown }[] = [] - const device = declaredDeviceSubstitutes({ deviceState: { notificationTray: [entry] } }) - device.bind((name, value) => effects.push({ name, value })) - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the declared tray lists exactly these two members. - const module = device.substitutes.get('expo-notifications') as { - getPresentedNotificationsAsync: () => Promise - dismissNotificationAsync: (identifier: string) => Promise - } - const presented = await module.getPresentedNotificationsAsync() - expect(presented).toEqual([entry]) - expect(presented[0]).not.toBe(entry) - await module.dismissNotificationAsync('tray-1') - expect(effects).toEqual([ - { name: 'notification-tray.dismiss', value: { identifier: 'tray-1' } } - ]) - }) - it('backs a recorded read: the declared last-visited repo is the one the dialog selects', async () => { const recording = await record('new-workspace-repositories-fulfilled') expect(sentParams(recording)).toEqual({ name: 'repo.list#1', params: { $rpc: 'absent' } }) diff --git a/mobile/src/test-support/rpc-recording/family-recordings.test.ts b/mobile/src/test-support/rpc-recording/family-recordings.test.ts index a3fcc0dd268..e49310dad70 100644 --- a/mobile/src/test-support/rpc-recording/family-recordings.test.ts +++ b/mobile/src/test-support/rpc-recording/family-recordings.test.ts @@ -2,7 +2,6 @@ import { resolve } from 'node:path' import { describe, expect, it } from 'vitest' import { readScenarios } from './scenario-input' import { familyGoldens } from './derived-goldens' -import { REPLY_MATRIX_NORMAL_RESULT_INVENTORY } from './reply-matrix-normal-result' import { runRecording } from './run-recording' import { pilotMountAdapters } from './pilot-mount-adapters' import { vitestRecordingScheduler } from './vitest-recording-scheduler' @@ -47,20 +46,6 @@ async function certify(id: string, scenarios: RecordingScenario[]) { describe('family reply partitions and owned schedules', () => { const goldens = familyGoldens(input.scenarios) - const families = [...new Set(input.scenarios.map((scenario) => scenario.family))] - // Count sites that actually generate tests so skipped families cannot hide behind the matrix-site reader. - const sites = goldens.flatMap((golden) => (golden.site ? [golden] : [])) - it('matrices every family in the manifest', () => { - expect([...new Set(sites.map((golden) => golden.family))]).toEqual(families) - }) - // The inventory is only consulted for a live site, so a stale entry would retire silently. - it('lists only live matrix sites in the normal-result inventory', () => { - const live = new Set(sites.map((golden) => `${golden.family}\0${golden.site}`)) - const stale = REPLY_MATRIX_NORMAL_RESULT_INVENTORY.filter( - (entry) => !live.has(`${entry.family}\0${entry.request}`) - ).map((entry) => `${entry.family} ${entry.request}`) - expect(stale).toEqual([]) - }) for (const golden of goldens) { it( golden.title, diff --git a/mobile/src/test-support/rpc-recording/inventory-lifecycle-derivation.test.ts b/mobile/src/test-support/rpc-recording/inventory-lifecycle-derivation.test.ts deleted file mode 100644 index dc0a653a8e2..00000000000 --- a/mobile/src/test-support/rpc-recording/inventory-lifecycle-derivation.test.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { resolve } from 'node:path' -import { expect, it } from 'vitest' -import { familyGoldens } from './derived-goldens' -import { hoistPreludeCheckpoints } from './prelude-checkpoints' -import { readScenarios } from './scenario-input' -import { bindCompletions, lifecycleSchedules } from './schedule-driver' - -it('omits only inventory admission following a refusal to a retired owner', () => { - const manifest = readScenarios( - resolve(import.meta.dirname, '../../../rpc-foundation/pilot-scenarios.json') - ) - const base = manifest.scenarios.find((scenario) => scenario.id === 'inventory-lifecycle') - if (!base) { - throw new Error('missing inventory base scenario') - } - const golden = familyGoldens(manifest.scenarios).find( - (item) => item.id === 'lifecycle-inventory-lifecycle' - ) - if (!golden) { - throw new Error('missing inventory lifecycle golden') - } - const original = hoistPreludeCheckpoints( - { ...base, steps: bindCompletions(base.steps) }, - (['reset', 'unmount', 'blur'] as const).flatMap((action) => lifecycleSchedules(base, action)) - ) - const derived = golden.scenarios() - expect(derived.map((scenario) => scenario.id)).toEqual(original.map((scenario) => scenario.id)) - expect(derived).toHaveLength(12) - for (const [index, scenario] of original.entries()) { - const unmounted = scenario.id === 'inventory-lifecycle.unmount-before-1' - if (!unmounted && scenario.id !== 'inventory-lifecycle.reset-before-1') { - expect(derived[index]).toEqual(scenario) - expect(JSON.stringify(derived[index])).toBe(JSON.stringify(scenario)) - continue - } - expect(scenario.steps).toHaveLength(unmounted ? 13 : 11) - expect(scenario.steps.slice(7, 10)).toEqual([ - { bind: 'old-inventory', request: 'files.list#1', params: { worktree: 'id:A' } }, - { bind: 'lifecycle-old-inventory', request: 'old-inventory', params: { worktree: 'id:A' } }, - { - complete: 'lifecycle-old-inventory', - params: { worktree: 'id:A' }, - reply: { ok: true, result: { files: [{ relativePath: 'old.ts' }] } } - } - ]) - const expected = { - ...scenario, - steps: [...scenario.steps.slice(0, 7), ...scenario.steps.slice(10)] - } - expect(derived[index]).toEqual(expected) - expect(JSON.stringify(derived[index])).toBe(JSON.stringify(expected)) - expect(expected.steps.filter((step) => 'checkpoint' in step)).toEqual([ - { checkpoint: 'lifecycle-boundary' }, - { checkpoint: 'settled' }, - ...(unmounted ? [{ checkpoint: 'remounted' }] : []) - ]) - expect(expected.steps.filter((step) => 'bind' in step)).toEqual([ - { - bind: 'lifecycle-files.searchPaths#1', - request: 'files.searchPaths#1', - params: { worktree: 'id:A', query: 'old', limit: 16 } - } - ]) - } -}) diff --git a/mobile/src/test-support/rpc-recording/mounted-screen-tree.test.ts b/mobile/src/test-support/rpc-recording/mounted-screen-tree.test.ts index fa5d7859eda..02be330f34c 100644 --- a/mobile/src/test-support/rpc-recording/mounted-screen-tree.test.ts +++ b/mobile/src/test-support/rpc-recording/mounted-screen-tree.test.ts @@ -1,12 +1,7 @@ import { resolve } from 'node:path' import { createElement } from 'react' import { describe, expect, it } from 'vitest' -import { - hookScreenMount, - projectMountedScreen, - renderedElementProps, - screenMount -} from './mounted-screen-tree' +import { hookScreenMount, projectMountedScreen, screenMount } from './mounted-screen-tree' import { pilotMountAdapters } from './pilot-mount-adapters' import { runRecording } from './run-recording' import { readScenarios } from './scenario-input' @@ -42,36 +37,6 @@ describe('a mounted screen', () => { }) }) - it('projects the elements, copy and labels of what rendered', () => { - const screen = screenMount( - () => - createElement( - 'View', - null, - createElement('Text', { accessibilityLabel: 'title' }, 'Files'), - createElement('Text', null, 'orca-files') - ), - effectSink().effect - ) - screen.mount() - expect(projectMountedScreen(screen)).toEqual({ - elements: { View: 1, Text: 2 }, - text: ['Files', 'orca-files'], - labels: ['title'], - crash: null - }) - }) - - it('reads the props an inert element was handed, which is all a list ever renders', () => { - const screen = screenMount( - () => createElement('View', null, createElement('FlatList', { data: [{ id: 'row-1' }] })), - effectSink().effect - ) - screen.mount() - expect(renderedElementProps(screen.tree(), 'FlatList')).toEqual([{ data: [{ id: 'row-1' }] }]) - expect(renderedElementProps(screen.tree(), 'SectionList')).toEqual([]) - }) - // Hook projections omit crashes; the effect sink must record them independently. it('reports a crash through the effect sink with no adapter cooperation', () => { const sink = effectSink() diff --git a/mobile/src/test-support/rpc-recording/mutants/family-mutants.test.ts b/mobile/src/test-support/rpc-recording/mutants/family-mutants.test.ts index 94df0c518e6..b3347fcc0bf 100644 --- a/mobile/src/test-support/rpc-recording/mutants/family-mutants.test.ts +++ b/mobile/src/test-support/rpc-recording/mutants/family-mutants.test.ts @@ -17,20 +17,18 @@ const input = readScenarios( const goldens = process.env.RPC_FOUNDATION_GOLDENS ?? resolve(root, 'mobile/rpc-foundation/goldens') // Compare the whole variant: malformed-container mutants may differ only at settlement and recover to identical final state. -// Pin both a killing and a blind partition; row-shape requirements remain unit coverage because matrices vary envelopes only. +// Row-shape requirements remain unit coverage because matrices vary envelopes only. const MATRIX_MUTANTS: readonly { mutation: Mutation family: string site: string killedBy: string - blindTo: string }[] = [ { mutation: 'linear-status-nullable', family: 'tasks.provider-load', site: 'linear.status#1', - killedBy: 'result-null', - blindTo: 'normal' + killedBy: 'result-null' } ] @@ -78,8 +76,5 @@ describe('reply-matrix partitions hold the container requirements', () => { it(`${entry.family} ${entry.site}: ${entry.killedBy} kills ${entry.mutation}`, async () => { expect(await verdict(entry, entry.killedBy)).toBe('killed') }, 30_000) - it(`${entry.family} ${entry.site}: ${entry.blindTo} cannot see ${entry.mutation}`, async () => { - expect(await verdict(entry, entry.blindTo)).toBe('survived') - }, 30_000) } }) diff --git a/mobile/src/test-support/rpc-recording/mutants/mutant-seam.test.ts b/mobile/src/test-support/rpc-recording/mutants/mutant-seam.test.ts deleted file mode 100644 index 58317b20b04..00000000000 --- a/mobile/src/test-support/rpc-recording/mutants/mutant-seam.test.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { existsSync, readFileSync, readdirSync } from 'node:fs' -import { dirname, join, resolve, sep } from 'node:path' -import { describe, expect, it } from 'vitest' -import { RECORDING_DRIVERS } from '../recording-drivers' - -const root = resolve(import.meta.dirname, '../../../../..') -const recorder = join(root, 'mobile/src/test-support/rpc-recording') -const mutants = join(recorder, 'mutants') -/** Names the drivers for the record script and the suites that check them; no recording loads it. */ -const OFF_RECORDING_PATH = ['recording-drivers.ts'] - -function sources(directory: string): string[] { - return readdirSync(directory, { withFileTypes: true, recursive: true }) - .filter((entry) => entry.isFile() && entry.name.endsWith('.ts')) - .map((entry) => join(entry.parentPath, entry.name)) -} - -function resolved(from: string, specifier: string): string | undefined { - const base = resolve(dirname(from), specifier) - return ['', '.ts', '.tsx', '/index.ts'] - .map((suffix) => base + suffix) - .find((candidate) => existsSync(candidate) && /\.tsx?$/.test(candidate)) -} - -function relative(file: string): string { - return file.slice(recorder.length + 1) -} - -/** A suite that records nothing: excluded below, since no driver has reason to reach it. */ -function suite(file: string): boolean { - return file.endsWith('.test.ts') && !RECORDING_DRIVERS.some((driver) => file.endsWith(driver)) -} - -// Include type-position imports too; over-approximating reachability only strengthens the forbidden mutant boundary. -function reachable(entries: readonly string[]): Set { - const seen = new Set() - const pending = [...entries] - while (pending.length > 0) { - const file = pending.pop()! - if (seen.has(file)) { - continue - } - seen.add(file) - for (const match of readFileSync(file, 'utf8').matchAll(/(?:from|import\()\s*'(\.[^']*)'/g)) { - const target = resolved(file, match[1]!) - if (target) { - pending.push(target) - } - } - } - return seen -} - -// Walk outward from recording drivers so no recorded byte can depend on mutant code; scan names for non-import reads too. -describe('the mutant seam', () => { - const outside = sources(recorder).filter((file) => !file.startsWith(`${mutants}${sep}`)) - - it('is unreachable from every recording driver', () => { - const graph = reachable(RECORDING_DRIVERS.map((driver) => join(recorder, driver))) - const reached = [...graph] - .filter((file) => file.startsWith(`${mutants}${sep}`)) - .map(relative) - .sort() - expect(reached).toEqual([]) - // Require every recorder module to be reachable so an empty graph cannot pass. - const missed = outside - .filter((file) => !suite(file) && !graph.has(file)) - .filter((file) => !OFF_RECORDING_PATH.includes(relative(file))) - .map(relative) - .sort() - expect(missed).toEqual([]) - expect(sources(mutants).length).toBeGreaterThan(1) - }) - - // Apply the engine boundary to recording drivers; other tests cannot alter recorded bytes. - it('is named in no recording file', () => { - const naming = outside - .filter((file) => !suite(file) && readFileSync(file, 'utf8').includes('mutants')) - .map(relative) - expect(naming).toEqual([]) - }) -}) diff --git a/mobile/src/test-support/rpc-recording/mutants/pilot-mutants.test.ts b/mobile/src/test-support/rpc-recording/mutants/pilot-mutants.test.ts index c983a4c93a1..403b8f046d2 100644 --- a/mobile/src/test-support/rpc-recording/mutants/pilot-mutants.test.ts +++ b/mobile/src/test-support/rpc-recording/mutants/pilot-mutants.test.ts @@ -3,7 +3,7 @@ import { describe, expect, it } from 'vitest' import { pilotGoldens } from '../derived-goldens' import { readGolden } from '../golden-recording' import { pilotMountAdapters } from '../pilot-mount-adapters' -import { runRecording, runRecordingMutant } from '../run-recording' +import { runRecordingMutant } from '../run-recording' import { readScenarios } from '../scenario-input' import { vitestRecordingScheduler } from '../vitest-recording-scheduler' import { operationMutation, type Mutation } from './operation-mutations' @@ -42,26 +42,6 @@ const mutants: Record = { 'worktree-catalog-snapshot-unreadable': 'worktree-catalog-unchecked-reader', 'mobile-web-bundle-fetch-paged': 'mobile-web-bundle-chunk-placement' } -// Pin archived defect states so unrelated reference refactors cannot pass merely by differing. -const referenceStates: Record = { - b1: { files: [] }, - b2: { - error: '', - mutating: false, - row: { - content: { - assignees: [], - labels: [{ color: '808080', name: 'recorded' }], - number: 1, - repository: 'owner/repo' - }, - id: 'item-1', - itemType: 'ISSUE' - } - }, - b3: { error: 'issue refused', loading: false, payload: { $rpc: 'null' } } -} - function visibleState(recording: Recording): RecordedValue { return recording.checkpoints.at(-1)!.observation.state } @@ -71,11 +51,6 @@ const mutantPilots = pilots.flatMap((pilot) => { const mutation = mutants[pilot.id] return mutation ? [{ ...pilot, mutation }] : [] }) -const referencePilots = pilots.flatMap((pilot) => { - const reference = referenceStates[pilot.id] - return reference ? [{ ...pilot, reference }] : [] -}) - describe('RPC main recording mutants', () => { for (const { id, scenario, mutation } of mutantPilots) { it(`${id}: kills ${mutation}`, async () => { @@ -94,19 +69,4 @@ describe('RPC main recording mutants', () => { expect(result.verdict).toBe('killed') }) } - for (const { id, scenario, reference } of referencePilots) { - it.skipIf(!process.env.RPC_FOUNDATION_REFERENCE_ROOT)(`${id}: rejects bcba08b3e4`, async () => { - const { adapters } = pilotMountAdapters(process.env.RPC_FOUNDATION_REFERENCE_ROOT!, { - device: scenario, - reference: true - }) - const result = await runRecording( - scenario, - adapters[scenario.operation], - vitestRecordingScheduler() - ) - expect(visibleState(result)).toEqual(reference) - expect(reference).not.toEqual(visibleState(readGolden(goldens, id).recording)) - }) - } }) diff --git a/mobile/src/test-support/rpc-recording/mutants/probe-hole-witness.test.ts b/mobile/src/test-support/rpc-recording/mutants/probe-hole-witness.test.ts index 693636b427d..072b8aa4a46 100644 --- a/mobile/src/test-support/rpc-recording/mutants/probe-hole-witness.test.ts +++ b/mobile/src/test-support/rpc-recording/mutants/probe-hole-witness.test.ts @@ -14,7 +14,7 @@ const input = readScenarios( ) const goldens = process.env.RPC_FOUNDATION_GOLDENS ?? resolve(root, 'mobile/rpc-foundation/goldens') -// Prove each probe closes a surviving mutation and that earlier scenarios stayed blind. +// Prove each probe closes a surviving mutation. // Empty session inputs exercise omission guards that always-filled fixtures could not reach. const HOLES: readonly { mutation: Mutation; operation: string; closedBy: readonly string[] }[] = [ { @@ -96,38 +96,11 @@ async function verdict(id: string, mutation: Mutation): Promise { } describe('probe scenarios close holes the pre-probe recordings left open', () => { - // Every completion needs a later checkpoint so a params-mismatch abort always suppresses an observation the golden actually holds. - it('never completes a request after the last checkpoint of a scenario', () => { - const trailing = input.scenarios - .filter((scenario) => { - let lastComplete = -1 - let lastCheckpoint = -1 - scenario.steps.forEach((step, index) => { - if ('complete' in step) { - lastComplete = index - } - // A step may carry both, and then the checkpoint records what the completion produced. - if ('checkpoint' in step) { - lastCheckpoint = index - } - }) - return lastComplete > lastCheckpoint - }) - .map((scenario) => scenario.id) - expect(trailing).toEqual([]) - }) - for (const hole of HOLES) { - const family = input.scenarios.filter((scenario) => scenario.operation === hole.operation) for (const id of hole.closedBy) { it(`${id} kills ${hole.mutation}`, async () => { expect(await verdict(id, hole.mutation)).toBe('killed') }) } - for (const scenario of family.filter(({ id }) => !hole.closedBy.includes(id))) { - it(`${scenario.id} cannot see ${hole.mutation}`, async () => { - expect(await verdict(scenario.id, hole.mutation)).toBe('survived') - }) - } } }) diff --git a/mobile/src/test-support/rpc-recording/native-mounting-substitutes.test.ts b/mobile/src/test-support/rpc-recording/native-mounting-substitutes.test.ts index c912ab6e859..cb1c02dd4a3 100644 --- a/mobile/src/test-support/rpc-recording/native-mounting-substitutes.test.ts +++ b/mobile/src/test-support/rpc-recording/native-mounting-substitutes.test.ts @@ -26,13 +26,6 @@ describe('nativeMountingSubstitutes', () => { ) }) - it('leaves the interop marker undefined so a default import binds the module, not the trap', () => { - for (const name of ['@react-native-async-storage/async-storage', 'expo-crypto']) { - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: reads the marker the emit reads, off a proxy with no declared shape. - expect((substitute(name) as { __esModule?: unknown }).__esModule).toBeUndefined() - } - }) - it('throws on a member nobody substituted rather than recording an undefined native API', () => { // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the read itself is the assertion; the proxy has no declared shape. expect(() => (substitute('expo-crypto') as { randomUUID?: unknown }).randomUUID).toThrow( diff --git a/mobile/src/test-support/rpc-recording/operation-module-loader-work-budget.test.ts b/mobile/src/test-support/rpc-recording/operation-module-loader-work-budget.test.ts index e9dc9921883..f57e295cf04 100644 --- a/mobile/src/test-support/rpc-recording/operation-module-loader-work-budget.test.ts +++ b/mobile/src/test-support/rpc-recording/operation-module-loader-work-budget.test.ts @@ -1,5 +1,4 @@ -import { mkdtempSync, mkdirSync, rmSync, writeFileSync, existsSync } from 'node:fs' -import type * as FsModule from 'node:fs' +import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' import { afterEach, expect, it, vi } from 'vitest' @@ -7,11 +6,6 @@ import { nativeMountingSubstitutes } from './native-mounting-substitutes' import type * as NativeSubstitutesModule from './native-mounting-substitutes' import { operationModuleLoader } from './operation-module-loader' -vi.mock('node:fs', async (original) => { - const fs = await original() - return { ...fs, existsSync: vi.fn(fs.existsSync) } -}) - vi.mock('./native-mounting-substitutes', async (original) => { const module = await original() return { ...module, nativeMountingSubstitutes: vi.fn(module.nativeMountingSubstitutes) } @@ -36,22 +30,6 @@ afterEach(() => { vi.clearAllMocks() }) -it('resolves a relative import once instead of probing disk on every exported member read', () => { - const root = sourceTree({ - 'value.ts': 'export const value = 42', - 'operation.ts': `import { value } from './value'; export const read = () => value` - }) - const operation = operationModuleLoader(root).load<{ read: () => number }>( - 'mobile/src/operation.ts' - ) - expect(operation.read()).toBe(42) - vi.mocked(existsSync).mockClear() - for (let index = 0; index < 1_000; index++) { - expect(operation.read()).toBe(42) - } - expect(existsSync).not.toHaveBeenCalled() -}) - it('builds device substitutes only for a used loader and keeps their state per loader', async () => { const root = sourceTree({ 'operation.ts': ` diff --git a/mobile/src/test-support/rpc-recording/rpc-recording-through-bridge.test.ts b/mobile/src/test-support/rpc-recording/rpc-recording-through-bridge.test.ts index ca4efa599ad..c7bc50b13e5 100644 --- a/mobile/src/test-support/rpc-recording/rpc-recording-through-bridge.test.ts +++ b/mobile/src/test-support/rpc-recording/rpc-recording-through-bridge.test.ts @@ -21,7 +21,6 @@ import { import { C5_PAGE_CLOSURE } from '../bridged-parity/c5-page-closure' import { C6_BROWSER_CLOSURE_FAMILIES } from '../bridged-parity/c6-browser-closure-families' import { C2_PAGE_CLOSURE } from '../bridged-parity/c2-page-closure' -import { C1_PAGE_CLOSURE } from '../bridged-parity/c1-page-closure' import { C3_PAGE_CLOSURE } from '../bridged-parity/c3-page-closure' import { pageClosureDrift, @@ -303,16 +302,6 @@ describe.skipIf(process.env[BRIDGED_PARITY_FLAG] === BRIDGED_PARITY_OFF)( expect({ tally: bridgedParityTallyDrift({ identical, counts }) }).toEqual({ tally: [] }) }) - it('gives every golden the C1 page closure records the verdict it is pinned to', () => { - process.stdout.write(readPageClosure('C1', C1_PAGE_CLOSURE, observed)) - // Pin every closure id; aggregate corpus totals can trade a domain regression for an unrelated improvement. - expect({ closure: pageClosureDrift(C1_PAGE_CLOSURE, observed) }).toEqual({ closure: [] }) - // Check run totals independently because C1’s table has no separate class-count pins. - expect(pageClosureRunTotals(C1_PAGE_CLOSURE, observed)).toEqual( - pageClosureTotals(C1_PAGE_CLOSURE) - ) - }) - it('gives every golden the C5 page closure records the verdict it is pinned to', () => { process.stdout.write(readPageClosure('C5', C5_PAGE_CLOSURE, observed)) // Recheck inherited C1 families alongside C5’s additions so shared regressions fail both closures. diff --git a/mobile/src/test-support/rpc-recording/screen-native-substitutes.test.ts b/mobile/src/test-support/rpc-recording/screen-native-substitutes.test.ts index 6785be7b1dc..4cc6388b045 100644 --- a/mobile/src/test-support/rpc-recording/screen-native-substitutes.test.ts +++ b/mobile/src/test-support/rpc-recording/screen-native-substitutes.test.ts @@ -1,9 +1,8 @@ import { createElement, type ElementType } from 'react' import { act, create } from 'react-test-renderer' import { describe, expect, it } from 'vitest' -import { inertIconModule } from './inert-native-elements' import { nativeMountingSubstitutes } from './native-mounting-substitutes' -import { reactNativeScreenMembers, screenNativeSubstitutes } from './screen-native-substitutes' +import { reactNativeScreenMembers } from './screen-native-substitutes' function member(module: string, name: string): unknown { const substitute = nativeMountingSubstitutes().get(module) @@ -68,44 +67,4 @@ describe('the inert screen substitutes', () => { // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: a rendered host node carries exactly these fields. expect((tree as { children: unknown }).children).toBeNull() }) - - it('pins the device inputs a screen reads instead of measuring them', () => { - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the member is a hook by construction. - expect((member('react-native', 'useWindowDimensions') as () => unknown)()).toEqual({ - width: 390, - height: 844 - }) - expect(member('react-native', 'StyleSheet')).toMatchObject({ hairlineWidth: 1 }) - }) - - it('keeps a style sheet readable so a screen that reads one style sees it', () => { - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the member is the substituted StyleSheet by construction. - const sheet = member('react-native', 'StyleSheet') as { - create: (value: unknown) => unknown - flatten: (value: unknown) => unknown - } - expect(sheet.create({ row: { flex: 1 } })).toEqual({ row: { flex: 1 } }) - expect(sheet.flatten([{ flex: 1 }, [{ gap: 2 }]])).toEqual({ flex: 1, gap: 2 }) - }) - - it('answers any icon name with its own element, because every export here is an icon', () => { - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the icon module answers every string key. - const icons = inertIconModule() as Record - expect(icons.ChevronLeft).toBe(icons.ChevronLeft) - act(() => { - create(createElement(icons.ChevronLeft!, { size: 20 })) - }) - }) - - it('throws on a member nobody listed, in every screen package', () => { - for (const [module, substitute] of screenNativeSubstitutes()) { - if (module === 'lucide-react-native') { - continue - } - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the read is the assertion; a substitute proxy has no declared shape. - expect(() => (substitute as Record).notASubstitutedMember).toThrow( - `Unsubstituted native member: ${module}.notASubstitutedMember` - ) - } - }) }) diff --git a/mobile/src/test-support/rpc-recording/scripted-client-wrapper.test.ts b/mobile/src/test-support/rpc-recording/scripted-client-wrapper.test.ts index 92b9fc3f168..b67c9af6967 100644 --- a/mobile/src/test-support/rpc-recording/scripted-client-wrapper.test.ts +++ b/mobile/src/test-support/rpc-recording/scripted-client-wrapper.test.ts @@ -32,13 +32,4 @@ describe('a wrapper between the operation and the scripted transport', () => { expect(inner).not.toBeNull() expect(() => inner?.('alpha.one')).toThrow(/cannot take the name \(no logical request\)/) }) - - it('names each physical send after the logical call that made it', async () => { - const transport = new ScriptedRpcTransport() - void transport.client.sendRequest('alpha.one') - void transport.client.sendRequest('beta.two') - await Promise.resolve() - await Promise.resolve() - expect(transport.payloads.map((payload) => payload.name)).toEqual(['alpha.one#1', 'beta.two#1']) - }) }) diff --git a/mobile/src/test-support/rpc-recording/subscription-recording.test.ts b/mobile/src/test-support/rpc-recording/subscription-recording.test.ts index b7580ebef36..2f74d061d47 100644 --- a/mobile/src/test-support/rpc-recording/subscription-recording.test.ts +++ b/mobile/src/test-support/rpc-recording/subscription-recording.test.ts @@ -2,8 +2,6 @@ import { describe, expect, it } from 'vitest' import { RpcClientStreamRegistry } from '../../transport/rpc-client-stream-registry' import { ScriptedRpcTransport } from './scripted-rpc-transport' import { vitestRecordingScheduler } from './vitest-recording-scheduler' -import { driveReplyMatrix, replyMatrixGoldenId, replyMatrixSites } from './reply-matrix' -import { replyMatrixNormalResult } from './reply-matrix-normal-result' import { runRecording } from './run-recording' import type { RpcClient } from '../../transport/rpc-client' import type { RecordingScenario } from './recording-scenario' @@ -229,56 +227,6 @@ describe('subscription recordings', () => { // Due-timer cleanup must match synchronous cleanup rather than appear as an uncancelled stream. expect(await drive('timer')).toEqual([]) }) - - it('drives the reply matrix over frames, and matrixes a family that only subscribes', () => { - const changed = { ok: true, streaming: true, result: { type: 'worktreesChanged' } } - const base: RecordingScenario = { - id: 'stream', - operation: 'op', - family: 'op', - sites: [], - schedules: [], - steps: [ - { action: 'mount', id: 'mount' }, - { frame: `${CLIENT_EVENTS}#1`, params: null, reply: readyFrame('sub-1') }, - { checkpoint: 'ready' }, - { frame: `${CLIENT_EVENTS}#1`, params: null, reply: changed }, - { checkpoint: 'changed' } - ] - } - expect(replyMatrixSites(base)).toEqual([`${CLIENT_EVENTS}#1@1`, `${CLIENT_EVENTS}#1@2`]) - expect(replyMatrixGoldenId('op', `${CLIENT_EVENTS}#1@2`)).toBe( - 'matrix-op-runtime.clientevents.subscribe-1-2' - ) - expect(replyMatrixNormalResult('op', [base], `${CLIENT_EVENTS}#1@2`)).toEqual(changed.result) - const variants = driveReplyMatrix(base, `${CLIENT_EVENTS}#1@1`, readyFrame('sub-1').result) - const partitions = variants.map((variant) => variant.id.replace('stream.', '')) - // Frames have no promise, so the two request-rejection partitions do not apply. - expect(partitions).toEqual([ - 'normal', - 'result-absent', - 'result-null', - 'inner-ok-missing', - 'inner-false-string-error', - 'inner-false-object-error', - 'outer-refused', - 'outer-refused-no-message', - 'method-not-found' - ]) - const replies = new Map(variants.map((variant) => [variant.id, variant.steps[1]])) - // Keep streaming:true on success controls so they route to the same stream. - expect(replies.get('stream.normal')).toEqual(base.steps[1]) - expect(replies.get('stream.result-absent')).toEqual({ - frame: `${CLIENT_EVENTS}#1`, - params: null, - reply: { ok: true, streaming: true } - }) - expect(replies.get('stream.outer-refused')).toMatchObject({ - reply: { ok: false, error: { code: 'refused' } } - }) - // Frames stay required after divergence because the registry still routes them to the opening id. - expect(variants[0]!.steps[3]).toEqual(base.steps[3]) - }) }) const CLIENT_EVENTS = 'runtime.clientEvents.subscribe' diff --git a/mobile/src/transport/cellular-handshake-stall-real-socket.test.ts b/mobile/src/transport/cellular-handshake-stall-real-socket.test.ts deleted file mode 100644 index 7d628a5a98c..00000000000 --- a/mobile/src/transport/cellular-handshake-stall-real-socket.test.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { WebSocketServer, type WebSocket as ServerWebSocket } from 'ws' -import { connect } from './rpc-client' -import { classifyConnection, verdictDisplayLabel } from './connection-health' - -// Guard fake-timer mock drift with real WebSocket upgrades and delayed E2EE replies (#10119). -// Opt in with ORCA_MOBILE_LIVE_REPRO=1; this needs real timers and roughly 22 seconds. - -vi.mock('./e2ee', () => ({ - generateKeyPair: () => ({ publicKey: new Uint8Array(32), secretKey: new Uint8Array(32) }), - deriveSharedKey: () => new Uint8Array(32), - publicKeyFromBase64: () => new Uint8Array(32), - publicKeyToBase64: () => 'client-public-key', - encrypt: (plaintext: string) => plaintext, - decrypt: (raw: string) => raw, - decryptBytes: (bytes: Uint8Array) => bytes -})) - -const RUN_LIVE = - process.env.ORCA_MOBILE_LIVE_REPRO === '1' || !!process.env.ORCA_MOBILE_LIVE_REPRO_FULL - -// Allow three handshake timeout cycles plus CI scheduling slack. -const OBSERVE_MS = 22_000 -const SAMPLE_MS = 200 - -let server: WebSocketServer | null = null -const serverSockets: ServerWebSocket[] = [] - -// Upgrade succeeds but e2ee_ready arrives after the handshake budget. -async function startSlowHandshakeDesktop(replyAfterMs: number): Promise { - const wss = new WebSocketServer({ host: '127.0.0.1', port: 0 }) - server = wss - wss.on('connection', (socket) => { - serverSockets.push(socket) - socket.on('message', (data) => { - const text = data.toString() - if (!text.includes('e2ee_hello')) { - return - } - setTimeout(() => { - if (socket.readyState === socket.OPEN) { - socket.send(JSON.stringify({ type: 'e2ee_ready', publicKeyB64: 'server-public-key' })) - } - }, replyAfterMs) - }) - }) - await new Promise((resolve) => wss.once('listening', resolve)) - const address = wss.address() - if (typeof address === 'string' || address === null) { - throw new Error('expected a TCP address') - } - return address.port -} - -describe.runIf(RUN_LIVE)('issue #10119 — real socket, handshake slower than the 5s budget', () => { - beforeEach(() => { - serverSockets.length = 0 - vi.spyOn(console, 'log').mockImplementation(() => {}) - }) - - afterEach(async () => { - vi.restoreAllMocks() - for (const socket of serverSockets) { - socket.terminate() - } - const wss = server - server = null - if (wss) { - await new Promise((resolve) => wss.close(() => resolve())) - } - }) - - it('escalates and latches against a live desktop that answers 1s too late', async () => { - const port = await startSlowHandshakeDesktop(6_000) - const endpoint = `ws://127.0.0.1:${port}` - const client = connect(endpoint, 'device-token', 'server-public-key') - - const labels: string[] = [] - const states: string[] = [] - let maxAttempts = 0 - const started = Date.now() - while (Date.now() - started < OBSERVE_MS) { - const state = client.getState() - const attempts = client.getReconnectAttempt() - maxAttempts = Math.max(maxAttempts, attempts) - states.push(state) - labels.push( - verdictDisplayLabel( - classifyConnection({ - state, - reconnectAttempts: attempts, - lastConnectedAt: client.getLastConnectedAt(), - endpoint - }) - ) - ) - await new Promise((resolve) => setTimeout(resolve, SAMPLE_MS)) - } - client.close() - - expect(serverSockets.length).toBeGreaterThanOrEqual(2) - expect(states).toContain('handshaking') - expect(states).not.toContain('connected') - - expect(maxAttempts).toBeGreaterThanOrEqual(3) - const firstEscalated = labels.findIndex((l) => l !== 'Connecting…' && l !== 'Reconnecting…') - expect(firstEscalated).toBeGreaterThan(0) - - expect(labels.slice(firstEscalated).every((l) => l !== 'Connecting…')).toBe(true) - }, 60_000) -}) diff --git a/mobile/src/transport/lifecycle-owner.test.ts b/mobile/src/transport/lifecycle-owner.test.ts index d2cae90ef6c..2a34d00152c 100644 --- a/mobile/src/transport/lifecycle-owner.test.ts +++ b/mobile/src/transport/lifecycle-owner.test.ts @@ -1,9 +1,4 @@ -import { readFileSync } from 'node:fs' -import { fileURLToPath } from 'node:url' -import { extname, join, relative, resolve } from 'node:path' -import ts from 'typescript-api' import { describe, expect, it } from 'vitest' -import { censusSourceFiles } from '../test-support/census-source-files' import { GenerationScopedRequestOwner, type LoadedRequest, @@ -83,23 +78,6 @@ describe('key-reset-cleanup', () => { }) }) -describe('blur', () => { - it('refuses a reply that settles after a blur, without the scope having moved', async () => { - const owner: Owner = new GenerationScopedRequestOwner() - const scope = scopeAt('w1', 1) - const request = pending() - const loaded = owner.load(scope, QUERY, request.start) - - // Blur retires the surface generation without changing host/workspace identity. - owner.reset() - - request.resolve(['stale.ts']) - const lease = await settled(loaded) - expect(owner.commit(lease.lease, lease.value)).toBe('retired-generation') - expect(owner.read(scope, QUERY)).toBeUndefined() - }) -}) - describe('cutover', () => { it('retires the whole scope when the logical authority epoch advances', async () => { const owner: Owner = new GenerationScopedRequestOwner() @@ -340,178 +318,3 @@ describe('currency probe', () => { expect(owner.read(scope, QUERY)).toEqual(['sent.ts']) }) }) - -const mobileRoot = fileURLToPath(new URL('../..', import.meta.url)) -const ownerModule = join(mobileRoot, 'src', 'transport', 'generation-scoped-request-owner') - -function importsOwner(path: string, source: string): boolean { - const parsed = ts.createSourceFile(path, source, ts.ScriptTarget.Latest, true) - return parsed.statements.some((statement) => { - const specifier = ts.isImportDeclaration(statement) ? statement.moduleSpecifier : undefined - return ( - specifier !== undefined && - ts.isStringLiteral(specifier) && - specifier.text.startsWith('.') && - resolve(path, '..', specifier.text) === ownerModule - ) - }) -} - -function declaredInside(callback: ts.Node): Set { - const names = new Set() - const bind = (name: ts.BindingName): void => { - if (ts.isIdentifier(name)) { - names.add(name.text) - return - } - for (const element of name.elements) { - if (ts.isBindingElement(element)) { - bind(element.name) - } - } - } - const visit = (node: ts.Node): void => { - if (ts.isVariableDeclaration(node) || ts.isParameter(node)) { - bind(node.name) - } - if ((ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node)) && node.name) { - names.add(node.name.text) - } - if (ts.isCatchClause(node) && node.variableDeclaration) { - bind(node.variableDeclaration.name) - } - ts.forEachChild(node, visit) - } - ts.forEachChild(callback, visit) - return names -} - -function assignmentRoot(target: ts.Expression): string | null { - let node: ts.Expression = target - for (;;) { - if (ts.isIdentifier(node)) { - return node.text - } - if (node.kind === ts.SyntaxKind.ThisKeyword) { - return 'this' - } - if (ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node)) { - node = node.expression - continue - } - if (ts.isParenthesizedExpression(node) || ts.isNonNullExpression(node)) { - node = node.expression - continue - } - return null - } -} - -function externalWrites(callback: ts.Node): string[] { - const declared = declaredInside(callback) - const offenders: string[] = [] - const record = (target: ts.Expression): void => { - const root = assignmentRoot(target) - if (root !== null && !declared.has(root)) { - offenders.push(root) - } - } - const visit = (node: ts.Node): void => { - if (ts.isBinaryExpression(node) && node.operatorToken.kind >= ts.SyntaxKind.FirstAssignment) { - if (node.operatorToken.kind <= ts.SyntaxKind.LastAssignment) { - record(node.left) - } - } - if ( - (ts.isPrefixUnaryExpression(node) || ts.isPostfixUnaryExpression(node)) && - (node.operator === ts.SyntaxKind.PlusPlusToken || - node.operator === ts.SyntaxKind.MinusMinusToken) - ) { - record(node.operand) - } - ts.forEachChild(node, visit) - } - ts.forEachChild(callback, visit) - return offenders -} - -function loaders(path: string, source: string): ts.Node[] { - const parsed = ts.createSourceFile( - path, - source, - ts.ScriptTarget.Latest, - true, - extname(path) === '.tsx' ? ts.ScriptKind.TSX : ts.ScriptKind.TS - ) - const found: ts.Node[] = [] - const visit = (node: ts.Node): void => { - if ( - ts.isCallExpression(node) && - ts.isPropertyAccessExpression(node.expression) && - node.expression.name.text === 'load' - ) { - const loader = node.arguments[2] - if (loader && (ts.isArrowFunction(loader) || ts.isFunctionExpression(loader))) { - found.push(loader) - } - } - ts.forEachChild(node, visit) - } - ts.forEachChild(parsed, visit) - return found -} - -function loaderWrites(path: string, source: string): string[] { - return loaders(path, source).flatMap((loader) => externalWrites(loader)) -} - -describe('loader write fence', () => { - const holders = ['app', 'src'] - .map((directory) => join(mobileRoot, directory)) - .flatMap(censusSourceFiles) - .filter((path) => ['.ts', '.tsx'].includes(extname(path))) - .filter((path) => !/\.test\.tsx?$/.test(path)) - .map((path) => ({ path, source: readFileSync(path, 'utf8') })) - .filter(({ path, source }) => importsOwner(path, source)) - - it('recognizes a loader that writes outside itself and leaves an honest one alone', () => { - const probe = join(mobileRoot, 'src', 'transport', 'probe.ts') - expect( - loaderWrites(probe, 'owner.load(scope, {}, async () => { cacheRef.current = await read() })') - ).toEqual(['cacheRef']) - expect(loaderWrites(probe, 'owner.load(scope, {}, async () => { sequence++ })')).toEqual([ - 'sequence' - ]) - expect( - loaderWrites(probe, 'owner.load(scope, {}, async () => { this.paths = await read() })') - ).toEqual(['this']) - expect( - loaderWrites( - probe, - 'owner.load(scope, {}, async () => { const rows = await read(); return rows })' - ) - ).toEqual([]) - }) - - it('has every owner holder loading without writing external state', () => { - // Require discovered owners/loaders so an empty scan cannot pass vacuously. - expect( - holders.map(({ path }) => relative(mobileRoot, path).split(/[/\\]/).join('/')) - ).toContain('src/session/native-chat/composer/use-mobile-native-chat-file-search.ts') - expect( - holders.reduce((total, { path, source }) => total + loaders(path, source).length, 0) - ).toBeGreaterThan(0) - const offenders = holders - .map(({ path, source }) => ({ - file: relative(mobileRoot, path).split(/[/\\]/).join('/'), - writes: loaderWrites(path, source) - })) - .filter((entry) => entry.writes.length > 0) - .map((entry) => `${entry.file}: ${[...new Set(entry.writes)].sort().join(', ')}`) - .sort() - expect( - offenders, - 'Return the value from the loader and publish it with commit(lease, value) instead.' - ).toEqual([]) - }) -}) diff --git a/mobile/src/transport/workspace-server.test.ts b/mobile/src/transport/workspace-server.test.ts deleted file mode 100644 index b2b65a0935a..00000000000 --- a/mobile/src/transport/workspace-server.test.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { createElement } from 'react' -import { act, create, type ReactTestRenderer } from 'react-test-renderer' -import { describe, expect, it, vi } from 'vitest' -import type { MobileRelayHost } from '../../../src/shared/mobile-relay-hosts-contract' -import type { RpcClient } from './rpc-client' -import type { ConnectionState } from './types' -import { stubRpcClient } from './rpc-client-test-stub' - -const feed = vi.hoisted((): { hosts: readonly MobileRelayHost[] | null } => ({ hosts: null })) -const gatesConnStates = vi.hoisted((): ConnectionState[] => []) - -vi.mock('../worktree/relay-hosts-feed', () => ({ useRelayHosts: () => feed.hosts })) -vi.mock('./execution-host-scoped-rpc-client', () => ({ - workspaceRouteExecutionHost: (_client: unknown, _caps: unknown, host: string) => host, - scopeRpcClientToExecutionHost: (client: RpcClient) => client -})) -vi.mock('./host-status-gates', () => ({ - useHostStatusGates: (args: { connState: ConnectionState }) => { - gatesConnStates.push(args.connState) - return {} - } -})) - -const { useWorkspaceServer } = await import('./workspace-server') - -const HOST = 'runtime:server-1' as const - -function server(relay: MobileRelayHost['relay']): MobileRelayHost { - return { hostId: HOST, label: 'Server', health: 'available', relay } -} - -describe('useWorkspaceServer', () => { - it("re-reads the server's status when the server reconnects under a connected desktop", async () => { - const client = stubRpcClient() - function Probe(): null { - useWorkspaceServer({ - client, - connState: 'connected', - desktopCapabilities: [], - executionHost: HOST - }) - return null - } - feed.hosts = [server('ready')] - let renderer: ReactTestRenderer | undefined - await act(async () => { - renderer = create(createElement(Probe)) - }) - feed.hosts = [server('unavailable')] - await act(async () => { - renderer?.update(createElement(Probe)) - }) - feed.hosts = [server('ready')] - await act(async () => { - renderer?.update(createElement(Probe)) - }) - // The gates' status read is keyed on this state, so the restart must reach it. - expect(gatesConnStates).toEqual(['connected', 'connecting', 'connected']) - renderer?.unmount() - }) -}) diff --git a/src/main/automations/dispatch-refusal.test.ts b/src/main/automations/dispatch-refusal.test.ts deleted file mode 100644 index ec682b3681e..00000000000 --- a/src/main/automations/dispatch-refusal.test.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { afterEach, describe, expect, it, vi } from 'vitest' -import type { Automation, AutomationRun } from '../../shared/automations-types' -import type { AutomationRunWriter } from './automation-run-writer' -import { UNEVALUABLE_SCHEDULE, recordUnevaluableAutomation } from './dispatch-refusal' - -const brokenAutomation: Automation = { - id: 'a1', - name: 'Broken schedule', - prompt: 'Check the repo', - precheck: null, - agentId: 'claude', - projectId: 'r1', - executionTargetType: 'local', - executionTargetId: 'local', - schedulerOwner: 'local_host_service', - workspaceMode: 'existing', - workspaceId: 'wt1', - baseBranch: null, - reuseSession: false, - timezone: 'UTC', - rrule: '0 9 32 * *', - dtstart: 0, - enabled: true, - nextRunAt: 1000, - missedRunPolicy: 'run_once_within_grace', - missedRunGraceMinutes: 720, - createdAt: 0, - updatedAt: 0 -} - -const makeRun = (id: string): AutomationRun => ({ - id, - automationId: brokenAutomation.id, - title: 'Broken schedule run', - scheduledFor: brokenAutomation.nextRunAt, - status: 'pending', - trigger: 'scheduled', - workspaceId: brokenAutomation.workspaceId, - sessionKind: 'terminal', - chatSessionId: null, - terminalSessionId: null, - terminalPaneKey: null, - terminalPtyId: null, - outputSnapshot: null, - precheckResult: null, - usage: null, - error: null, - startedAt: null, - dispatchedAt: null, - createdAt: 0 -}) - -function makeRunWriter(foldsRepeat: boolean): { - writer: AutomationRunWriter - created: string[] - updated: { status: string; error?: string | null }[] -} { - const created: string[] = [] - const updated: { status: string; error?: string | null }[] = [] - const writer: AutomationRunWriter = { - advanceNextRun: async () => brokenAutomation, - repeatSkip: async () => (foldsRepeat ? makeRun('folded') : null), - createRun: async () => { - const run = makeRun(`run-${created.length + 1}`) - created.push(run.id) - return run - }, - updateRun: async (args) => { - updated.push({ status: args.status, error: args.error }) - return { ...makeRun(args.runId), status: args.status, error: args.error ?? null } - } - } - return { writer, created, updated } -} - -describe('recordUnevaluableAutomation', () => { - afterEach(() => { - vi.restoreAllMocks() - }) - - it('writes one run and logs once when the record is newly broken', async () => { - const logged = vi.spyOn(console, 'error').mockImplementation(() => {}) - const { writer, created, updated } = makeRunWriter(false) - - await recordUnevaluableAutomation({ - runs: writer, - automation: brokenAutomation, - error: new Error('Invalid cron day of month.') - }) - - expect(created).toEqual(['run-1']) - expect(updated).toEqual([{ status: 'skipped_unavailable', error: UNEVALUABLE_SCHEDULE }]) - expect(logged).toHaveBeenCalledTimes(1) - }) - - it('stays silent on a record it has already reported', async () => { - const logged = vi.spyOn(console, 'error').mockImplementation(() => {}) - const { writer, created } = makeRunWriter(true) - - for (let tick = 0; tick < 5; tick += 1) { - await recordUnevaluableAutomation({ - runs: writer, - automation: brokenAutomation, - error: new Error('Invalid cron day of month.') - }) - } - - expect(created).toEqual([]) - expect(logged).not.toHaveBeenCalled() - }) -}) diff --git a/src/main/automations/external-automation-probe-scheduler.test.ts b/src/main/automations/external-automation-probe-scheduler.test.ts index 21c8d29e610..abdc4da515f 100644 --- a/src/main/automations/external-automation-probe-scheduler.test.ts +++ b/src/main/automations/external-automation-probe-scheduler.test.ts @@ -1,8 +1,7 @@ import { describe, expect, it, vi } from 'vitest' import { ExternalAutomationProbeCancelledError, - ExternalAutomationProbeScheduler, - isExternalAutomationProbeCancelled + ExternalAutomationProbeScheduler } from './external-automation-probe-scheduler' function deferred(): { promise: Promise; resolve: (value: T) => void } { @@ -71,58 +70,6 @@ describe('ExternalAutomationProbeScheduler', () => { expect(run).toHaveBeenCalledTimes(1) }) - it('cancels queued and in-flight probes whose scope is no longer selected', async () => { - const scheduler = new ExternalAutomationProbeScheduler({ concurrency: 1 }) - const never = deferred() - const queuedRun = vi.fn(() => Promise.resolve('queued')) - const keptRun = vi.fn(() => Promise.resolve('local')) - - const inFlight = scheduler.schedule({ - key: 'openclaw@ssh', - scopeKey: 'owner:desktop:ssh:target-a:3', - run: () => never.promise - }) - const queued = scheduler.schedule({ - key: 'openclaw@ssh', - scopeKey: 'owner:desktop:ssh:target-a:3', - run: queuedRun - }) - const kept = scheduler.schedule({ - key: 'openclaw@self', - scopeKey: 'owner:desktop:self', - run: keptRun - }) - - scheduler.retainScopes(['owner:desktop:self']) - - await expect(inFlight).rejects.toBeInstanceOf(ExternalAutomationProbeCancelledError) - await expect(queued).rejects.toSatisfy(isExternalAutomationProbeCancelled) - expect(queuedRun).not.toHaveBeenCalled() - expect(keptRun).not.toHaveBeenCalled() - expect(scheduler.inFlight).toBe(1) - - never.resolve('ignored') - await expect(kept).resolves.toBe('local') - expect(scheduler.inFlight).toBe(0) - }) - - it('cancels everything on demand', async () => { - const scheduler = new ExternalAutomationProbeScheduler({ concurrency: 1 }) - const never = deferred() - const probe = scheduler.schedule({ - key: 'openclaw@self', - scopeKey: 'owner:desktop:self', - run: () => never.promise - }) - - scheduler.cancelAll() - - await expect(probe).rejects.toBeInstanceOf(ExternalAutomationProbeCancelledError) - expect(scheduler.inFlight).toBe(1) - never.resolve('ignored') - await vi.waitFor(() => expect(scheduler.inFlight).toBe(0)) - }) - it('does not start a duplicate key while cancelled provider work is still settling', async () => { const scheduler = new ExternalAutomationProbeScheduler({ concurrency: 2 }) const never = deferred() diff --git a/src/main/automations/external-manager-scoped.test.ts b/src/main/automations/external-manager-scoped.test.ts index ff625c14e1a..8c4e6159724 100644 --- a/src/main/automations/external-manager-scoped.test.ts +++ b/src/main/automations/external-manager-scoped.test.ts @@ -53,10 +53,6 @@ function desktopSsh(targetId: string, targetGeneration: number): AutomationOwner return { authority: { kind: 'desktop' }, selector: { kind: 'ssh', targetId, targetGeneration } } } -function orcaStoreHealth() { - return { read: vi.fn<() => void>(), markUnavailable: vi.fn<() => void>() } -} - function buildEngine(targets: SshTarget[]) { return createScopedExternalAutomations({ registry: { getSshTargets: () => targets }, @@ -125,39 +121,6 @@ describe('scoped external automations', () => { expect([...files]).toEqual(before) }) - it('settles when a local command lookup hangs', async () => { - vi.useFakeTimers() - runProcessMock.mockResolvedValue({ - code: null, - signal: null, - stdout: '', - stderr: '', - timedOut: true - }) - const promise = buildEngine([]).listManager({ owner: desktopSelf, provider: 'openclaw' }) - let settled = false - void promise.finally(() => { - settled = true - }) - - await expect(promise).resolves.toMatchObject({ manager: null }) - expect(settled).toBe(true) - }) - - it('probes only local managers when Local is selected', async () => { - const engine = buildEngine([sshTarget(), sshTarget({ id: 'target-b', generation: 1 })]) - - const entry = await engine.listManager({ owner: desktopSelf, provider: 'openclaw' }) - - expect(entry.manager?.id).toBe('openclaw:local') - expect(runProcessMock).toHaveBeenCalledTimes(1) - expect(runProcessMock.mock.calls[0]?.[0]).toEqual({ - program: process.platform === 'win32' ? 'where' : 'which', - args: ['openclaw'], - timeoutMs: 5_000 - }) - }) - it('reports an SSH host this process does not serve without probing this computer', async () => { const engine = buildEngine([sshTarget(), sshTarget({ id: 'target-b', generation: 1 })]) @@ -233,28 +196,4 @@ describe('scoped external automations', () => { ).rejects.toBeInstanceOf(AutomationOwnerConflictError) expect(runProcessMock).not.toHaveBeenCalled() }) - - it('confines a manager failure to its own scope and never to Orca store health', async () => { - const health = orcaStoreHealth() - const engine = createScopedExternalAutomations({ - registry: { - getSshTargets: () => { - health.read() - return [sshTarget()] - } - }, - scheduler: new ExternalAutomationProbeScheduler(), - cache: new ExternalAutomationManagerCache({ ttlMs: 0 }) - }) - - const failed = await engine.listManager({ - owner: desktopSsh('target-a', 3), - provider: 'openclaw' - }) - const healthy = await engine.listManager({ owner: desktopSelf, provider: 'openclaw' }) - - expect(failed).toMatchObject({ manager: null, error: SSH_HOST_NOT_SERVED_MESSAGE }) - expect(healthy.manager?.status).toBe('available') - expect(health.markUnavailable).not.toHaveBeenCalled() - }) }) diff --git a/src/main/automations/external-manager.test.ts b/src/main/automations/external-manager.test.ts index 25d63ac25a2..6b84ea91e9f 100644 --- a/src/main/automations/external-manager.test.ts +++ b/src/main/automations/external-manager.test.ts @@ -37,22 +37,6 @@ afterEach(() => { }) describe('runExternalAutomationAction', () => { - it('runs local OpenClaw lifecycle actions through the CLI', async () => { - await runExternalAutomationAction({ - managerId: 'openclaw:local', - provider: 'openclaw', - target: { type: 'local' }, - jobId: 'job-1', - action: 'run' - }) - - expect(runProcessMock).toHaveBeenCalledWith({ - program: 'openclaw', - args: ['cron', 'run', 'job-1'], - timeoutMs: 30_000 - }) - }) - it('rejects job IDs that could be parsed as CLI options', async () => { await expect( runExternalAutomationAction({ @@ -109,22 +93,6 @@ describe('runExternalAutomationAction', () => { expect(runProcessMock).not.toHaveBeenCalled() }) - it('maps OpenClaw lifecycle actions through its cron CLI names', async () => { - await runExternalAutomationAction({ - managerId: 'openclaw:local', - provider: 'openclaw', - target: { type: 'local' }, - jobId: 'job-1', - action: 'pause' - }) - - expect(runProcessMock).toHaveBeenCalledWith({ - program: 'openclaw', - args: ['cron', 'disable', 'job-1'], - timeoutMs: 30_000 - }) - }) - it('refuses actions on an SSH host without launching locally', async () => { await expect( runExternalAutomationAction({ diff --git a/src/main/automations/headless-automation-agent-launch.test.ts b/src/main/automations/headless-automation-agent-launch.test.ts index f4876f7689d..2776dfa0292 100644 --- a/src/main/automations/headless-automation-agent-launch.test.ts +++ b/src/main/automations/headless-automation-agent-launch.test.ts @@ -117,45 +117,6 @@ function launch( } describe('headless automation agent launch', () => { - it('launches Hermes through the ordinary agent contract', async () => { - const runtime = harness() - - const launched = await launch(runtime, { agentId: 'hermes' }) - - expect(runtime.launchAgentTerminal).toHaveBeenCalledWith('id:wt-1', { - agent: 'hermes', - prompt: 'fix the flaky test', - title: 'Nightly', - suppressStartupUpdatePrompt: true - }) - expect(runtime.deliverStartupFollowup).not.toHaveBeenCalled() - expect(launched.terminalHandle).toBe('term_existing') - }) - - it('starts an argv agent in an existing workspace with the prompt on its command', async () => { - const runtime = harness() - - const launched = await launch(runtime, { extraAgentArgs: '--model opus' }) - - expect(runtime.launchAgentTerminal).toHaveBeenCalledWith('id:wt-1', { - agent: 'claude', - prompt: 'fix the flaky test', - title: 'Nightly', - suppressStartupUpdatePrompt: true, - extraAgentArgs: '--model opus' - }) - expect(runtime.deliverStartupFollowup).not.toHaveBeenCalled() - expect(runtime.getStructuredAgentSessionCreateSupport).not.toHaveBeenCalled() - expect(launched).toEqual({ - workspaceId: 'wt-1', - workspaceDisplayName: 'repo', - terminalHandle: 'term_existing', - terminalSessionId: 'tab-1', - terminalPaneKey: 'tab-1:leaf-1', - terminalPtyId: 'pty-1' - }) - }) - it('starts a post-start agent bare and delivers its prompt once, after the terminal is recorded', async () => { const runtime = harness() let finishDelivery: (delivered: boolean) => void = () => {} @@ -178,54 +139,6 @@ describe('headless automation agent launch', () => { finishDelivery(true) }) - it('creates a new-per-run workspace agent-first with the argv prompt', async () => { - const runtime = harness() - - const launched = await launch(runtime, { - workspaceMode: 'new_per_run', - workspaceId: null, - extraAgentArgs: '--model opus' - }) - - expect(runtime.createManagedWorktree).toHaveBeenCalledTimes(1) - const args = runtime.createManagedWorktree.mock.calls[0]![0] - expect(args).toMatchObject({ - repoSelector: 'repo-1', - activate: false, - setupDecision: 'skip', - startupAgent: 'claude', - startupPrompt: 'fix the flaky test', - startupExtraAgentArgs: '--model opus', - createdWithAgent: 'claude', - telemetrySource: 'unknown' - }) - expect(args).not.toHaveProperty('awaitTerminalProvisioning') - expect(args).not.toHaveProperty('observeSetupCompletion') - expect(runtime.launchAgentTerminal).not.toHaveBeenCalled() - expect(runtime.deliverStartupFollowup).not.toHaveBeenCalled() - expect(launched).toEqual({ - workspaceId: 'repo-1::/wt/auto', - workspaceDisplayName: 'auto-nightly', - terminalHandle: 'term_new', - terminalSessionId: 'tab-new', - terminalPaneKey: 'tab-new:leaf-1', - terminalPtyId: 'pty-new' - }) - }) - - it('creates a new-per-run workspace for a post-start agent and delivers its prompt once', async () => { - const runtime = harness() - - await launch(runtime, { agentId: 'goose', workspaceMode: 'new_per_run', workspaceId: null }) - - expect(runtime.createManagedWorktree.mock.calls[0]![0]).not.toHaveProperty('startupPrompt') - expect(runtime.deliverStartupFollowup).toHaveBeenCalledTimes(1) - expect(runtime.deliverStartupFollowup).toHaveBeenCalledWith('term_new', { - agent: 'goose', - prompt: 'fix the flaky test' - }) - }) - it('fails the run with the create warning when no agent terminal started, without a second one', async () => { const runtime = harness({ startupTerminal: null }) @@ -235,18 +148,6 @@ describe('headless automation agent launch', () => { expect(runtime.launchAgentTerminal).not.toHaveBeenCalled() }) - it('launches bare and delivers nothing for a blank prompt', async () => { - const runtime = harness() - - await launch(runtime, { agentId: 'aider', prompt: ' ' }) - - expect(runtime.launchAgentTerminal).toHaveBeenCalledWith( - 'id:wt-1', - expect.objectContaining({ prompt: '' }) - ) - expect(runtime.deliverStartupFollowup).not.toHaveBeenCalled() - }) - it('logs a failure after the terminal was recorded instead of dropping it', async () => { const runtime = harness() const warn = vi.spyOn(console, 'warn').mockImplementation(() => {}) diff --git a/src/main/automations/headless-run-terminal-retention.test.ts b/src/main/automations/headless-run-terminal-retention.test.ts index e2b5ddd1e1d..5558dccd76f 100644 --- a/src/main/automations/headless-run-terminal-retention.test.ts +++ b/src/main/automations/headless-run-terminal-retention.test.ts @@ -203,18 +203,6 @@ describe('headless run terminal retention', () => { expect(h.forgotten[0]).toMatchObject({ id: 'missing', status: 'dispatch_failed' }) }) - it('keeps a timed-out run whose agent still runs in its shell', async () => { - const runs = [ - makeRun('timed-out', 0, 'dispatch_failed'), - ...[1, 2, 3].map((n) => makeRun(`done${n}`, n)) - ] - const h = harness(runs) - await h.retention.sweep() - vi.advanceTimersByTime(RUN_TERMINAL_GRACE_MS * 10) - await h.retention.sweep() - expect(h.closed).toEqual([]) - }) - it('closes a still-dispatched run whose agent exited, leaving its shell idle', async () => { const runs = [ makeRun('exited', 0, 'dispatched'), @@ -354,17 +342,6 @@ describe('headless run terminal retention', () => { expect(h.forgotten.map((run) => run.id)).not.toContain('r0') }) - it('closes a completed run whose workspace is shared', async () => { - const runs = [0, 1, 2, 3].map((n) => makeRun(`r${n}`, n)) - const h = harness(runs) - await h.retention.sweep() - vi.advanceTimersByTime(RUN_TERMINAL_GRACE_MS) - await h.retention.sweep() - expect(h.slept).toEqual([]) - expect(h.closed).toEqual(['tab-r0:1']) - expect(h.forgotten.map((run) => run.id)).toEqual(['r0']) - }) - it('closes a failed run in its own workspace, and never sleeps or closes a used one', async () => { const runs = [ makeRun('failed', 0, 'dispatch_failed'), diff --git a/src/main/automations/headless-workspace-create.test.ts b/src/main/automations/headless-workspace-create.test.ts index 83e34d6b64c..8c2ffb3efe1 100644 --- a/src/main/automations/headless-workspace-create.test.ts +++ b/src/main/automations/headless-workspace-create.test.ts @@ -89,29 +89,4 @@ describe('headless automation workspace create args', () => { } }) }) - - it('falls back to skip for legacy automations without a saved setup decision', () => { - const args = buildHeadlessAutomationWorktreeCreateArgs({ - automation: { ...automation, setupDecision: undefined }, - run: { - id: 'run-1', - title: 'Nightly review run', - scheduledFor: Date.UTC(2026, 0, 2, 3, 4, 5) - }, - repo - }) - - expect(args.setupDecision).toBe('skip') - expect(Object.hasOwn(args, 'startupExtraAgentArgs')).toBe(false) - }) - - it('carries saved extras into the startup agent launch', () => { - const args = buildHeadlessAutomationWorktreeCreateArgs({ - automation: { ...automation, extraAgentArgs: '--model gpt-5.5' }, - run: { id: 'run-1', title: 'Nightly review run', scheduledFor: 0 }, - repo - }) - - expect(args.startupExtraAgentArgs).toBe('--model gpt-5.5') - }) }) diff --git a/src/main/automations/retained-run-reconciliation.test.ts b/src/main/automations/retained-run-reconciliation.test.ts index 2765459d861..03b03d56a36 100644 --- a/src/main/automations/retained-run-reconciliation.test.ts +++ b/src/main/automations/retained-run-reconciliation.test.ts @@ -4,11 +4,7 @@ import { mkdtempSync, rmSync } from 'node:fs' import { join } from 'node:path' import { tmpdir } from 'node:os' import type { Repo } from '../../shared/repo-types' -import { - isFinalAutomationRunStatus, - type Automation, - type AutomationRun -} from '../../shared/automations-types' +import type { Automation, AutomationRun } from '../../shared/automations-types' import { AutomationService } from './service' import type { AutomationRunCompletionObservation, @@ -156,44 +152,6 @@ describe('reconciling retained runs against a graph that has not published yet', service.stop() }) - it('persists the terminal status exactly once when the restored pane finishes the run', async () => { - const store = await createStore() - const automation = createAutomation(store) - const retained = retainDispatchedRun(store, automation) - const surface = createPaneSurface() - const service = new AutomationService(store, { terminalObserver: surface.observer }) - - const transitions: string[] = [] - const persist = store.updateAutomationRun.bind(store) - vi.spyOn(store, 'updateAutomationRun').mockImplementation((update) => { - const before = store.listAutomationRuns(automation.id).find((r) => r.id === update.runId) - if (before && !isFinalAutomationRunStatus(before.status)) { - if (isFinalAutomationRunStatus(update.status)) { - transitions.push(update.status) - } - } - return persist(update) - }) - - service.start() - service.setRendererReady() - await vi.advanceTimersByTimeAsync(5_000) - - surface.mountPane() - await service.markDispatchResult({ - runId: retained.id, - status: 'completed', - ...LAUNCH_TARGET, - error: null - }) - await vi.advanceTimersByTimeAsync(AFTER_SETTLE_MS) - - expect(transitions).toEqual(['completed']) - expect(readRun(store, automation.id, retained.id).status).toBe('completed') - expect(readRun(store, automation.id, retained.id).error).toBeNull() - service.stop() - }) - it('does not fail a serve-startup retained run whose host pane has not reattached', async () => { const store = await createStore() const automation = createAutomation(store) diff --git a/src/main/automations/run-completion-watcher.test.ts b/src/main/automations/run-completion-watcher.test.ts index 87a6adf30f6..66ea838eff1 100644 --- a/src/main/automations/run-completion-watcher.test.ts +++ b/src/main/automations/run-completion-watcher.test.ts @@ -344,23 +344,4 @@ describe('automationsChanged publication', () => { expect(seen.every((entry) => entry.payload.selector?.kind === 'self')).toBe(true) service.stop() }) - - it('changes nothing for a consumer that ignores the unknown event type', async () => { - const store = await createStore() - const automation = createAutomation(store) - const oldClientState = { repoRefreshes: 0 } - const service = new AutomationService(store, { - headlessDispatcher: async () => ({ ...LAUNCH_TARGET }), - onAutomationsChanged: (payload) => { - if ((payload as { type?: string }).type === 'reposChanged') { - oldClientState.repoRefreshes += 1 - } - } - }) - - const run = await service.runNow(automation.id) - expect(oldClientState.repoRefreshes).toBe(0) - expect(readRun(store, automation.id, run.id).status).toBe('dispatched') - service.stop() - }) }) diff --git a/src/main/automations/run-target-resolution.test.ts b/src/main/automations/run-target-resolution.test.ts index bebb4df7e3c..d87acb67d84 100644 --- a/src/main/automations/run-target-resolution.test.ts +++ b/src/main/automations/run-target-resolution.test.ts @@ -147,15 +147,6 @@ describe('resolveAutomationRunTarget projectId drift', () => { expect(result).toMatchObject({ ok: true, cwd: '/repo' }) }) - it('resolves when the snapshot is at the git: tier and the setup climbed to github:', () => { - const store = makeStore([makeSetup()], [makeRepo()]) - const automation = makeAutomation(makeRunContext({ projectId: 'git:github.com/o/r' })) - - const result = resolveAutomationRunTarget(store, automation) - - expect(result).toMatchObject({ ok: true, cwd: '/repo' }) - }) - it('still blocks when the repoId no longer matches the setup', () => { const store = makeStore([makeSetup({ repoId: 'repo-2' })], [makeRepo()]) const automation = makeAutomation(makeRunContext({ repoId: 'repo-1' })) diff --git a/src/main/automations/runtime-automation-service-retention.test.ts b/src/main/automations/runtime-automation-service-retention.test.ts index 19a6691ca3a..2b21371513f 100644 --- a/src/main/automations/runtime-automation-service-retention.test.ts +++ b/src/main/automations/runtime-automation-service-retention.test.ts @@ -195,22 +195,4 @@ describe('headless automation service run terminal retention', () => { expect(runtime.closeTerminal).not.toHaveBeenCalled() expect(service.markDispatchResult).not.toHaveBeenCalled() }) - - it('treats a retry that finds the run PTY already stopped by a failed sleep as slept', async () => { - const { runtime, service, store } = build(true) - store.getWorktreeMeta.mockImplementation(firstRunOwnsWorkspace) - store.listAutomationRuns.mockReturnValue(finishedRuns(1)) - // Simulate retrying a sleep that stopped the PTY before throwing. - runtime.sleepTerminalsForWorktree.mockResolvedValue({ livePtyIds: [], stoppedPtyIds: [] }) - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the mocked service exposes the drain hook it was given. - const drain = (service as unknown as { releaseFinishedRunTerminals: () => Promise }) - .releaseFinishedRunTerminals - await expect(drain()).resolves.toBe(1) - expect(runtime.closeTerminal).not.toHaveBeenCalled() - expect(service.markDispatchResult).toHaveBeenCalledWith({ - runId: 'r0', - status: 'completed', - terminalPtyId: null - }) - }) }) diff --git a/src/main/automations/runtime-automation-service.test.ts b/src/main/automations/runtime-automation-service.test.ts index 7ff99a18231..cc249a5d25c 100644 --- a/src/main/automations/runtime-automation-service.test.ts +++ b/src/main/automations/runtime-automation-service.test.ts @@ -1,5 +1,4 @@ import { describe, expect, it, vi } from 'vitest' -import type { AutomationRun } from '../../shared/automations-types' import type { HeadlessAutomationDispatcher } from './headless-dispatch' import type { AutomationRunTerminalObserver } from './run-completion-watcher' @@ -93,118 +92,3 @@ describe('headless automation dispatch', () => { ) }) }) - -describe('headless automation dispatch with extra agent args', () => { - it('passes saved extras to an existing-workspace launch', async () => { - const runtime = { - setAutomationService: vi.fn(), - notifyAutomationsChanged: vi.fn(), - launchAgentTerminal: vi.fn(async () => ({ - handle: 'terminal-1', - tabId: 'tab-1', - paneKey: 'tab-1:pane-1', - ptyId: 'pty-1', - worktreeId: 'wt-1' - })), - showManagedWorktree: vi.fn(async () => ({ displayName: 'repo' })), - getTerminalHandleForPaneKey: vi.fn(() => null), - getAgentStatusRowsForPane: vi.fn(() => []) - } - createRuntimeAutomationService({ - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the mocked service never reads the store; the dispatcher reads only past runs. - store: { listAutomationRuns: () => [] } as never, - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the dispatcher reads only the members faked above. - runtime: runtime as never, - headless: true - }) - const dispatcher = capturedOption('headlessDispatcher') - const automation = { - workspaceMode: 'existing', - workspaceId: 'wt-1', - agentId: 'claude', - prompt: 'go', - extraAgentArgs: '--model opus' - } - await dispatcher({ - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: only these fields are read. - automation: automation as never, - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: only the title is read. - run: { title: 'Nightly' } as never, - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: unused for an existing workspace. - target: {} as never - }) - - expect(runtime.launchAgentTerminal).toHaveBeenCalledWith('id:wt-1', { - agent: 'claude', - prompt: 'go', - title: 'Nightly', - suppressStartupUpdatePrompt: true, - extraAgentArgs: '--model opus' - }) - }) -}) - -describe('headless reuse-session dispatch', () => { - it('sends the second run into the agent the first run started, on one PTY', async () => { - const runs: AutomationRun[] = [] - const runtime = { - setAutomationService: vi.fn(), - notifyAutomationsChanged: vi.fn(), - launchAgentTerminal: vi.fn(async () => ({ - handle: 'terminal-1', - tabId: 'tab-1', - paneKey: 'tab-1:pane-1', - ptyId: 'pty-1', - worktreeId: 'wt-1' - })), - showManagedWorktree: vi.fn(async () => ({ displayName: 'repo' })), - getTerminalHandleForPaneKey: vi.fn(() => 'terminal-1'), - getTerminalPtyIdForHandle: vi.fn(() => 'pty-1'), - getAgentStatusRowsForPane: vi.fn(() => [ - { state: 'done', agentType: 'claude', receivedAt: 1 } - ]) - } - createRuntimeAutomationService({ - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the mocked service never reads the store; the dispatcher reads only past runs. - store: { listAutomationRuns: () => runs } as never, - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the dispatcher reads only the members faked above. - runtime: runtime as never, - headless: true - }) - const dispatcher = capturedOption('headlessDispatcher') - const dispatch = (id: string) => - dispatcher({ - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: only these fields are read. - automation: { - id: 'auto-1', - workspaceMode: 'existing', - workspaceId: 'wt-1', - agentId: 'claude', - prompt: 'triage', - reuseSession: true - } as never, - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: only the id and title are read. - run: { id, title: 'Nightly' } as never, - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: unused for an existing workspace. - target: {} as never - }) - - const first = await dispatch('run-1') - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: selection reads only these run fields. - runs.push({ - ...first, - id: 'run-1', - automationId: 'auto-1', - status: 'completed', - createdAt: 1 - } as AutomationRun) - const second = await dispatch('run-2') - - expect(runtime.launchAgentTerminal).toHaveBeenCalledTimes(1) - expect(deliverTerminalAgentLaunchPrompt).toHaveBeenCalledWith( - expect.objectContaining({ handle: 'terminal-1', freshLaunch: false, text: 'triage' }) - ) - expect(second.terminalPtyId).toBe(first.terminalPtyId) - expect(second.terminalPaneKey).toBe('tab-1:pane-1') - }) -}) diff --git a/src/main/automations/runtime-terminal-run-observer-evidence.test.ts b/src/main/automations/runtime-terminal-run-observer-evidence.test.ts index 488ff9ac9b6..c5fbbd38f7e 100644 --- a/src/main/automations/runtime-terminal-run-observer-evidence.test.ts +++ b/src/main/automations/runtime-terminal-run-observer-evidence.test.ts @@ -226,16 +226,6 @@ describe('observing a run with agent evidence', () => { expect(run.settled).toHaveBeenCalledWith(expect.objectContaining({ status: 'completed' })) }) - it('keeps idle-means-done for an agent that never reports, after its start window', async () => { - const pane = createPane({ idle: true, tail: ['summary written', '$'] }) - const run = observe(pane) - await vi.advanceTimersByTimeAsync(10_000) - expect(run.settled).not.toHaveBeenCalled() - await vi.advanceTimersByTimeAsync(AGENT_START_GRACE_MS) - await run.promise - expect(run.settled).toHaveBeenCalledWith(expect.objectContaining({ status: 'completed' })) - }) - it("never reads an agent's own tool output as its command missing", async () => { const tail = ['running tests', 'bash: pytest: command not found', 'fell back to unittest', '$'] const run = observe(createPane({ idle: true, tail })) diff --git a/src/main/automations/runtime-terminal-run-observer.test.ts b/src/main/automations/runtime-terminal-run-observer.test.ts index d8c29b95583..e8f638b8085 100644 --- a/src/main/automations/runtime-terminal-run-observer.test.ts +++ b/src/main/automations/runtime-terminal-run-observer.test.ts @@ -118,40 +118,6 @@ describe('createRuntimeAutomationRunTerminalObserver', () => { await run.promise }) - it('does not complete from a stale idle pane title (leaf branch shape)', async () => { - const runtime = createFakeRuntime({ lastAgentStatus: null, paneTitle: '✳ Claude — idle' }) - const run = observe(runtime) - - await vi.advanceTimersByTimeAsync(10_000) - expect(run.settled).toEqual([]) - - runtime.setPane({ paneTitle: '✳ Claude — working' }) - await vi.advanceTimersByTimeAsync(1_000) - expect(run.settled).toEqual([]) - - runtime.setPane({ paneTitle: '✳ Claude — idle' }) - await vi.advanceTimersByTimeAsync(1_000) - expect(run.settled[0]?.status).toBe('completed') - await run.promise - }) - - it('does not complete from a ready shell prompt left over at dispatch', async () => { - const runtime = createFakeRuntime({ preview: 'user@host repo %\n$' }) - const run = observe(runtime) - - await vi.advanceTimersByTimeAsync(10_000) - expect(run.settled).toEqual([]) - - runtime.setPane({ preview: 'claude is thinking…' }) - await vi.advanceTimersByTimeAsync(1_000) - expect(run.settled).toEqual([]) - - runtime.setPane({ lastAgentStatus: 'idle' }) - await vi.advanceTimersByTimeAsync(1_000) - expect(run.settled[0]?.status).toBe('completed') - await run.promise - }) - it('fails the run truthfully when the pane never leaves its pre-dispatch state', async () => { const runtime = createFakeRuntime({ lastAgentStatus: 'idle' }) const run = observe(runtime) diff --git a/src/main/automations/service-precheck.test.ts b/src/main/automations/service-precheck.test.ts index d638e9c6e44..f77d2da14fd 100644 --- a/src/main/automations/service-precheck.test.ts +++ b/src/main/automations/service-precheck.test.ts @@ -86,56 +86,6 @@ describe('AutomationService prechecks', () => { rmSync(testState.dir, { recursive: true, force: true }) }) - it('runs scheduled prechecks in the target repo before dispatch', async () => { - vi.setSystemTime(new Date('2026-05-13T08:00:00Z')) - const store = await createStore() - store.addRepo(makeRepo({ path: '/repo/path' })) - const automation = store.createAutomation({ - name: 'Conditional check', - prompt: 'Check the repo', - precheck: { - command: 'test -f ready', - timeoutSeconds: 30 - }, - agentId: 'claude', - projectId: 'r1', - workspaceMode: 'new_per_run', - timezone: 'UTC', - rrule: 'FREQ=DAILY;BYHOUR=9;BYMINUTE=0', - dtstart: new Date('2026-05-14T00:00:00Z').getTime() - }) - const run = store.createAutomationRun(automation, Date.now(), 'scheduled') - const precheckResult = { - command: 'test -f ready', - exitCode: 0, - timedOut: false, - durationMs: 5, - stdout: '', - stderr: '', - stdoutTruncated: false, - stderrTruncated: false, - error: null, - startedAt: Date.now(), - completedAt: Date.now() - } - runAutomationPrecheckMock.mockResolvedValue(precheckResult) - const service = new AutomationService(store, { tickMs: 60_000 }) - - const result = await service.runPrecheck(automation.id, run.id) - - expect(result).toEqual(precheckResult) - expect(runAutomationPrecheckMock).toHaveBeenCalledWith({ - precheck: { - command: 'test -f ready', - timeoutSeconds: 30 - }, - target: { - type: 'local', - cwd: '/repo/path' - } - }) - }) - it('does not run scheduled prechecks when the selected host setup is stale', async () => { vi.setSystemTime(new Date('2026-05-13T08:00:00Z')) const store = await createStore() diff --git a/src/main/azure-devops/azure-devops-api-request.test.ts b/src/main/azure-devops/azure-devops-api-request.test.ts index df79bd77b65..9b25f27a7a0 100644 --- a/src/main/azure-devops/azure-devops-api-request.test.ts +++ b/src/main/azure-devops/azure-devops-api-request.test.ts @@ -45,23 +45,6 @@ describe('Azure DevOps API request (STA-3494)', () => { globalThis.fetch = OLD_FETCH }) - it('retries with -preview when Azure DevOps Server rejects the api-version', async () => { - const versions: (string | null)[] = [] - globalThis.fetch = vi.fn(async (input: string | URL | Request) => { - const url = new URL(String(input)) - versions.push(url.searchParams.get('api-version')) - if (!url.searchParams.get('api-version')?.endsWith('-preview')) { - return previewRejection() - } - return Response.json({ authenticatedUser: { providerDisplayName: 'Server User' } }) - }) as never - - await expect( - requestAzureDevOpsJsonAtBase(SERVER_BASE, '/_apis/connectionData') - ).resolves.toEqual({ authenticatedUser: { providerDisplayName: 'Server User' } }) - expect(versions).toEqual(['7.1', '7.1-preview']) - }) - it('remembers the -preview requirement per origin after the first rejection', async () => { const versions: (string | null)[] = [] globalThis.fetch = vi.fn(async (input: string | URL | Request) => { diff --git a/src/main/azure-devops/client.test.ts b/src/main/azure-devops/client.test.ts index 9fa5fad71c0..264523bdb95 100644 --- a/src/main/azure-devops/client.test.ts +++ b/src/main/azure-devops/client.test.ts @@ -3,8 +3,7 @@ import { cancelTrackingResponse } from '../lib/unread-response-body.test-fixture import { getAzureDevOpsAuthStatus, getAzureDevOpsPullRequestForBranch, - getAzureDevOpsPullRequestForBranchOrThrow, - normalizeAzureDevOpsApiBaseUrl + getAzureDevOpsPullRequestForBranchOrThrow } from './client' import { _resetAzureDevOpsPreviewApiVersionCache } from './azure-devops-api-request' import { _resetAzureDevOpsRepoRefCache } from './repository-ref' @@ -46,23 +45,6 @@ describe('Azure DevOps client', () => { _resetAzureDevOpsRepoRefCache() }) - it('normalizes configured API base URLs', () => { - expect(normalizeAzureDevOpsApiBaseUrl('https://dev.azure.com/acme/Project/_apis/')).toBe( - 'https://dev.azure.com/acme/Project' - ) - }) - - it('marks token-only auth as configured but unverified because repository remotes supply the API base URL', async () => { - delete process.env.ORCA_AZURE_DEVOPS_API_BASE_URL - await expect(getAzureDevOpsAuthStatus()).resolves.toEqual({ - configured: true, - authenticated: false, - account: null, - baseUrl: null, - tokenConfigured: true - }) - }) - it('authenticates against an on-prem Server that requires -preview api-versions (STA-3494)', async () => { process.env.ORCA_AZURE_DEVOPS_API_BASE_URL = 'https://ado.example.com:8443/tfs/MyCollection' const versions: (string | null)[] = [] @@ -334,48 +316,6 @@ describe('Azure DevOps client', () => { ).resolves.toMatchObject({ number: 40, state: 'open' }) }) - it('keeps an active PR whose source branch is the repo default branch', async () => { - gitExecFileAsyncMock.mockResolvedValue({ - stdout: 'https://dev.azure.com/acme/Project/_git/repo\n' - }) - globalThis.fetch = vi.fn(async (input: string | URL | Request) => { - const url = new URL(String(input)) - const response = (body: unknown): Response => - new Response(JSON.stringify(body), { - status: 200, - headers: { 'Content-Type': 'application/json' } - }) - - if (url.pathname === '/acme/Project/_apis/git/repositories/repo') { - return response({ id: 'repo-guid' }) - } - if (url.pathname === '/acme/Project/_apis/git/repositories/repo-guid/pullRequests') { - return response({ - value: [ - { - pullRequestId: 31, - title: 'main → release', - status: 'active', - creationDate: '2026-05-10T00:00:00Z', - mergeStatus: 'succeeded', - lastMergeSourceCommit: { commitId: 'main-head-oid' } - } - ] - }) - } - if ( - url.pathname === '/acme/Project/_apis/git/repositories/repo-guid/pullRequests/31/statuses' - ) { - return response({ value: [{ state: 'succeeded' }] }) - } - return new Response(JSON.stringify({ message: 'not found' }), { status: 404 }) - }) as never - - await expect( - getAzureDevOpsPullRequestForBranch('/repo', 'refs/heads/main') - ).resolves.toMatchObject({ number: 31, state: 'open' }) - }) - it('cancels unread error-response bodies so bundled undici cannot crash on socket close', async () => { gitExecFileAsyncMock.mockResolvedValue({ stdout: 'https://dev.azure.com/acme/Project/_git/repo\n' diff --git a/src/main/azure-devops/pull-request-creation.test.ts b/src/main/azure-devops/pull-request-creation.test.ts index 894d80edf80..c9fb01ed367 100644 --- a/src/main/azure-devops/pull-request-creation.test.ts +++ b/src/main/azure-devops/pull-request-creation.test.ts @@ -1,8 +1,5 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { - createAzureDevOpsPullRequest, - isAzureDevOpsReviewCreationAuthenticated -} from './pull-request-creation' +import { createAzureDevOpsPullRequest } from './pull-request-creation' import { _resetAzureDevOpsPreviewApiVersionCache } from './azure-devops-api-request' import { _resetAzureDevOpsRepoRefCache } from './repository-ref' import { REMOTE_URL_PROBE_TIMEOUT_MS } from '../git/remote-url-probe' @@ -40,11 +37,6 @@ describe('Azure DevOps pull request creation', () => { _resetAzureDevOpsRepoRefCache() }) - it('treats token-only auth as sufficient for repo-scoped creation', () => { - delete process.env.ORCA_AZURE_DEVOPS_API_BASE_URL - expect(isAzureDevOpsReviewCreationAuthenticated()).toBe(true) - }) - it('posts a pull request create body to the repository REST endpoint', async () => { const fetchMock = vi.fn(async (input: string | URL | Request, init?: RequestInit) => { const url = new URL(String(input)) diff --git a/src/main/azure-devops/pull-request-mappers.test.ts b/src/main/azure-devops/pull-request-mappers.test.ts index 5d2f69bb1b6..a5054936f85 100644 --- a/src/main/azure-devops/pull-request-mappers.test.ts +++ b/src/main/azure-devops/pull-request-mappers.test.ts @@ -1,9 +1,5 @@ import { describe, expect, it } from 'vitest' -import { - deriveAzureDevOpsStatus, - mapAzureDevOpsPullRequest, - mapAzureDevOpsPullRequestState -} from './pull-request-mappers' +import { deriveAzureDevOpsStatus, mapAzureDevOpsPullRequestState } from './pull-request-mappers' describe('Azure DevOps pull request mappers', () => { it('maps Azure DevOps PR states into the hosted review state set', () => { @@ -19,30 +15,4 @@ describe('Azure DevOps pull request mappers', () => { expect(deriveAzureDevOpsStatus([{ state: 'succeeded' }, { state: 'failed' }])).toBe('failure') expect(deriveAzureDevOpsStatus([])).toBe('neutral') }) - - it('maps a raw PR into hosted review info', () => { - expect( - mapAzureDevOpsPullRequest( - { - pullRequestId: 42, - title: 'Add Azure support', - status: 'active', - creationDate: '2026-05-15T12:00:00Z', - mergeStatus: 'succeeded', - lastMergeSourceCommit: { commitId: 'abc123' } - }, - 'success', - 'https://dev.azure.com/acme/Project/_git/repo' - ) - ).toEqual({ - number: 42, - title: 'Add Azure support', - state: 'open', - url: 'https://dev.azure.com/acme/Project/_git/repo/pullrequest/42', - status: 'success', - updatedAt: '2026-05-15T12:00:00Z', - mergeable: 'MERGEABLE', - headSha: 'abc123' - }) - }) }) diff --git a/src/main/azure-devops/repository-ref.test.ts b/src/main/azure-devops/repository-ref.test.ts index 425d2548059..e8c7bcac4a6 100644 --- a/src/main/azure-devops/repository-ref.test.ts +++ b/src/main/azure-devops/repository-ref.test.ts @@ -136,17 +136,4 @@ describe('parseAzureDevOpsRepoRef', () => { timeout: REMOTE_URL_PROBE_TIMEOUT_MS }) }) - - it('bounds cached repository refs for distinct repo paths', async () => { - gitExecFileAsyncMock.mockResolvedValue({ - stdout: 'git@ssh.dev.azure.com:v3/acme/Project/repo\n', - stderr: '' - }) - - for (let i = 0; i < 513; i += 1) { - await getAzureDevOpsRepoRefForRemote(`/repo-${i}`, 'origin') - } - - expect(_getAzureDevOpsRepoRefCacheSize()).toBe(512) - }) }) diff --git a/src/main/bitbucket/client.test.ts b/src/main/bitbucket/client.test.ts index 3f9143e6db4..cdbd3eb77a3 100644 --- a/src/main/bitbucket/client.test.ts +++ b/src/main/bitbucket/client.test.ts @@ -82,44 +82,6 @@ describe('Bitbucket client', () => { await expect(getBitbucketPullRequestForBranch('/repo', 'refs/heads/main')).resolves.toBeNull() }) - it('keeps an OPEN PR whose source branch is the repo default branch', async () => { - const fetchMock = vi.fn(async (url: string) => { - if (url.includes('/statuses/build')) { - return Response.json({ values: [{ state: 'SUCCESSFUL' }] }) - } - return Response.json({ - values: [ - { ...bitbucketPr(8), source: { branch: { name: 'main' }, commit: { hash: 'abc123' } } } - ] - }) - }) - vi.stubGlobal('fetch', fetchMock) - - await expect( - getBitbucketPullRequestForBranch('/repo', 'refs/heads/main') - ).resolves.toMatchObject({ number: 8, state: 'open' }) - }) - - it('discards a MERGED default-branch shadow and refetches the linked PR via the fallback (#9171)', async () => { - primeGitExecWithDefaultBranch() - const fetchMock = vi.fn(async (url: string) => { - if (url.includes('/statuses/build')) { - return Response.json({ values: [] }) - } - if (url.endsWith('/pullrequests/42')) { - return Response.json(bitbucketPr(42)) - } - return Response.json({ - values: [{ ...bitbucketPr(7), state: 'MERGED', source: { branch: { name: 'main' } } }] - }) - }) - vi.stubGlobal('fetch', fetchMock) - - await expect( - getBitbucketPullRequestForBranch('/repo', 'refs/heads/main', 42) - ).resolves.toMatchObject({ number: 42 }) - }) - it('hides a MERGED PR matched only by feature-branch name so a new PR can be created', async () => { const fetchMock = vi.fn(async (url: string) => { if (url.includes('/statuses/build')) { @@ -289,37 +251,6 @@ describe('Bitbucket client', () => { ).rejects.toThrow(/Bitbucket request failed/) }) - it('falls back to a linked PR number when branch lookup misses', async () => { - const fetchMock = vi.fn(async (url: string, _init?: RequestInit) => { - if (url.includes('/statuses/build')) { - return Response.json({ values: [] }) - } - if (url.endsWith('/pullrequests/42')) { - return Response.json(bitbucketPr(42)) - } - return Response.json({ values: [] }) - }) - vi.stubGlobal('fetch', fetchMock) - - await expect(getBitbucketPullRequestForBranch('/repo', 'different', 42)).resolves.toMatchObject( - { - number: 42, - status: 'neutral' - } - ) - }) - - it('reports env-token auth status through the Bitbucket /user endpoint', async () => { - const fetchMock = vi.fn(async () => Response.json({ username: 'bitbucket-user' })) - vi.stubGlobal('fetch', fetchMock) - - await expect(getBitbucketAuthStatus()).resolves.toEqual({ - configured: true, - authenticated: true, - account: 'bitbucket-user' - }) - }) - it('cancels unread error-response bodies so bundled undici cannot crash on socket close', async () => { let cancelledBodies = 0 const fetchMock = vi.fn(async () => diff --git a/src/main/bitbucket/credential-connection.test.ts b/src/main/bitbucket/credential-connection.test.ts index 3987f15af16..495a875ec3a 100644 --- a/src/main/bitbucket/credential-connection.test.ts +++ b/src/main/bitbucket/credential-connection.test.ts @@ -150,36 +150,4 @@ describe('Bitbucket credential connection', () => { authMode: 'token' }) }) - - it('lets an env API base URL override the stored one without env credentials', async () => { - const conn = await loadModule() - globalThis.fetch = vi.fn(async () => - Response.json({ username: 'ada' }) - ) as unknown as typeof fetch - await conn.connectBitbucket({ - authMode: 'basic', - email: 'ada@example.com', - apiToken: 'tok', - baseUrl: 'https://stored.example.com/2.0' - }) - - process.env.ORCA_BITBUCKET_API_BASE_URL = 'https://env.example.com/2.0' - const { resolveBitbucketAuthConfig } = await import('./resolve-auth') - expect(resolveBitbucketAuthConfig().baseUrl).toBe('https://env.example.com/2.0') - }) - - it('clears the stored connection on disconnect', async () => { - const conn = await loadModule() - globalThis.fetch = vi.fn(async () => - Response.json({ username: 'ada' }) - ) as unknown as typeof fetch - await conn.connectBitbucket({ - authMode: 'basic', - email: 'ada@example.com', - apiToken: 'tok' - }) - - conn.disconnectBitbucket() - expect(conn.getBitbucketConnectionStatus().source).toBe('none') - }) }) diff --git a/src/main/bitbucket/credential-store.test.ts b/src/main/bitbucket/credential-store.test.ts index f216db3aabb..b30911401fd 100644 --- a/src/main/bitbucket/credential-store.test.ts +++ b/src/main/bitbucket/credential-store.test.ts @@ -82,31 +82,6 @@ beforeEach(() => { }) describe('Bitbucket credential store', () => { - it('persists plaintext metadata and an encrypted secret, then reads them back', async () => { - const store = await loadStore() - store.saveBitbucketCredential({ - authMode: 'basic', - email: 'ada@example.com', - baseUrl: null, - account: 'ada', - accessToken: null, - apiToken: 'secret-token' - }) - - expect(store.hasStoredBitbucketCredential()).toBe(true) - expect(store.getStoredBitbucketMetadata()).toMatchObject({ - authMode: 'basic', - email: 'ada@example.com', - account: 'ada' - }) - expect(store.loadStoredBitbucketSecret()).toMatchObject({ - accessToken: null, - apiToken: 'secret-token', - authMode: 'basic', - email: 'ada@example.com' - }) - }) - it('writes both credential files 0600', async () => { const store = await loadStore() store.saveBitbucketCredential({ diff --git a/src/main/bitbucket/pull-request-creation.test.ts b/src/main/bitbucket/pull-request-creation.test.ts index 5198813dd08..cd3568e1e11 100644 --- a/src/main/bitbucket/pull-request-creation.test.ts +++ b/src/main/bitbucket/pull-request-creation.test.ts @@ -1,8 +1,5 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { - createBitbucketPullRequest, - isBitbucketReviewCreationAuthenticated -} from './pull-request-creation' +import { createBitbucketPullRequest } from './pull-request-creation' import { _resetBitbucketRepoRefCache } from './repository-ref' const { gitExecFileAsyncMock } = vi.hoisted(() => ({ @@ -82,18 +79,6 @@ describe('Bitbucket pull request creation', () => { expect(fetchMock).toHaveBeenCalledTimes(1) }) - it('uses the stored credential when no environment variable is set', async () => { - delete process.env.ORCA_BITBUCKET_ACCESS_TOKEN - const fetchMock = vi.fn(async () => createdPullRequestResponse()) - globalThis.fetch = fetchMock as unknown as typeof fetch - - const result = await createBitbucketPullRequest('/repo', CREATE_INPUT, 'local') - - // This fixture has no stored credential after the environment token is removed. - expect(result).toMatchObject({ ok: false, code: 'auth_required' }) - expect(fetchMock).not.toHaveBeenCalled() - }) - it('ignores a draft request rather than dead-ending a hidden persisted default', async () => { const fetchMock = vi.fn(async () => createdPullRequestResponse()) globalThis.fetch = fetchMock as unknown as typeof fetch @@ -106,12 +91,6 @@ describe('Bitbucket pull request creation', () => { ) }) - it('reports authenticated only when a credential resolves', async () => { - expect(isBitbucketReviewCreationAuthenticated()).toBe(true) - delete process.env.ORCA_BITBUCKET_ACCESS_TOKEN - expect(isBitbucketReviewCreationAuthenticated()).toBe(false) - }) - it('maps a duplicate-branch rejection to already_exists with the existing review', async () => { let call = 0 const fetchMock = vi.fn(async () => { diff --git a/src/main/bitbucket/pull-request-mappers.test.ts b/src/main/bitbucket/pull-request-mappers.test.ts index fe329dfb181..dd3d146c2d9 100644 --- a/src/main/bitbucket/pull-request-mappers.test.ts +++ b/src/main/bitbucket/pull-request-mappers.test.ts @@ -1,9 +1,5 @@ import { describe, expect, it } from 'vitest' -import { - deriveBitbucketBuildStatus, - mapBitbucketPullRequest, - mapBitbucketPullRequestState -} from './pull-request-mappers' +import { deriveBitbucketBuildStatus, mapBitbucketPullRequestState } from './pull-request-mappers' describe('Bitbucket pull request mappers', () => { it('normalizes Bitbucket pull request states', () => { @@ -19,30 +15,4 @@ describe('Bitbucket pull request mappers', () => { expect(deriveBitbucketBuildStatus([{ state: 'INPROGRESS' }])).toBe('pending') expect(deriveBitbucketBuildStatus([{ state: 'FAILED' }])).toBe('failure') }) - - it('maps raw pull request JSON into the shared PR-like shape', () => { - expect( - mapBitbucketPullRequest( - { - id: 42, - title: 'Add Bitbucket', - state: 'MERGED', - updated_on: '2026-05-10T00:00:00.000Z', - links: { html: { href: 'https://bitbucket.org/team/repo/pull-requests/42' } }, - source: { branch: { name: 'feature' }, commit: { hash: 'abc123' } }, - destination: { branch: { name: 'main' } } - }, - 'success' - ) - ).toEqual({ - number: 42, - title: 'Add Bitbucket', - state: 'merged', - url: 'https://bitbucket.org/team/repo/pull-requests/42', - status: 'success', - updatedAt: '2026-05-10T00:00:00.000Z', - mergeable: 'UNKNOWN', - headSha: 'abc123' - }) - }) }) diff --git a/src/main/bitbucket/repository-ref.test.ts b/src/main/bitbucket/repository-ref.test.ts index 63b04c05232..139204eb9ad 100644 --- a/src/main/bitbucket/repository-ref.test.ts +++ b/src/main/bitbucket/repository-ref.test.ts @@ -108,17 +108,4 @@ describe('Bitbucket repository refs', () => { timeout: REMOTE_URL_PROBE_TIMEOUT_MS }) }) - - it('bounds cached repository refs for distinct repo paths', async () => { - gitExecFileAsyncMock.mockResolvedValue({ - stdout: 'git@bitbucket.org:team/project.git\n', - stderr: '' - }) - - for (let i = 0; i < 513; i += 1) { - await getBitbucketRepoRef(`/repo-${i}`) - } - - expect(_getBitbucketRepoRefCacheSize()).toBe(512) - }) }) diff --git a/src/main/browser-sidecar-owner.test.ts b/src/main/browser-sidecar-owner.test.ts index 74adf1c1317..4162f60acc8 100644 --- a/src/main/browser-sidecar-owner.test.ts +++ b/src/main/browser-sidecar-owner.test.ts @@ -40,8 +40,3 @@ it('removes the listener on disposal', () => { parent.emit('disconnect') expect(quit).not.toHaveBeenCalled() }) - -it('does not couple a desktop launch to a sidecar owner marker', () => { - installServeSupervisorDisconnectQuit(false, new EventEmitter()) - expect(quit).not.toHaveBeenCalled() -}) diff --git a/src/main/browser/agent-browser-bridge-capture-paint-hold.test.ts b/src/main/browser/agent-browser-bridge-capture-paint-hold.test.ts index 2809a3d30cb..4fd504a1418 100644 --- a/src/main/browser/agent-browser-bridge-capture-paint-hold.test.ts +++ b/src/main/browser/agent-browser-bridge-capture-paint-hold.test.ts @@ -100,23 +100,6 @@ describe('AgentBrowserBridge', () => { expect(holdPaintForCapture).not.toHaveBeenCalled() }) - it('gives the helper proxy a paint hold scoped to its page', async () => { - const release = vi.fn() - const holdPaintForCapture = vi.fn(() => release) - const b = new AgentBrowserBridge( - mockBrowserManager(undefined, undefined, { holdPaintForCapture }) - ) - b.setActiveTab(100) - webContentsFromIdMock.mockReturnValue(mockWebContents(100)) - - succeedWith({ snapshot: 'tree' }) - await b.snapshot() - - const holdPaint = CdpWsProxyMock.mock.calls[0]?.[1] - expect(holdPaint()).toBe(release) - expect(holdPaintForCapture).toHaveBeenCalledWith(100) - }) - it('captures full-page screenshots directly through CDP using CSS layout bounds', async () => { const wc = mockWebContents(100) wc.debugger.sendCommand.mockImplementation((method: string) => { diff --git a/src/main/browser/agent-browser-bridge-command-transport.test.ts b/src/main/browser/agent-browser-bridge-command-transport.test.ts index 7df8aa68958..32d54878fee 100644 --- a/src/main/browser/agent-browser-bridge-command-transport.test.ts +++ b/src/main/browser/agent-browser-bridge-command-transport.test.ts @@ -53,7 +53,6 @@ vi.mock('./cdp-bridge', () => ({ })) import { AgentBrowserBridge } from './agent-browser-bridge' -import { AGENT_BROWSER_IDLE_TIMEOUT_MS } from './agent-browser-process-environment' import { createFakeAgentBrowserChild, createSucceedWith, @@ -94,64 +93,6 @@ describe('AgentBrowserBridge', () => { bridge.setActiveTab(100) }) - it('uses browserPageId as session name', async () => { - succeedWith({ snapshot: '...' }) - await bridge.snapshot() - - const args = execFileMock.mock.calls[0][1] as string[] - expect(args).toContain('--session') - expect(args[args.indexOf('--session') + 1]).toBe('orca-tab-tab-1') - }) - - it('passes --cdp on every helper command family so a restarted daemon cannot launch Chrome', async () => { - succeedWith({ snapshot: '...' }) - await bridge.snapshot() - - const snapshotCall = execFileMock.mock.calls.find((c: unknown[]) => - (c[1] as string[]).includes('snapshot') - ) - expect(snapshotCall![1]).toContain('--cdp') - const cdpIdx = (snapshotCall![1] as string[]).indexOf('--cdp') - expect((snapshotCall![1] as string[])[cdpIdx + 1]).toBe('9222') - - await bridge.click('@e1') - await bridge.scroll('down') - await bridge.setOffline('on') - await bridge.consoleLog() - await bridge.exec('get title') - - for (const command of ['click', 'scroll', 'set', 'console', 'get']) { - const call = execFileMock.mock.calls.find((candidate: unknown[]) => - (candidate[1] as string[]).includes(command) - ) - expect(call).toBeDefined() - const args = call![1] as string[] - expect(args).toContain('--cdp') - expect(args[args.indexOf('--cdp') + 1]).toBe('9222') - } - }) - - it('always appends --json to commands', async () => { - succeedWith({ snapshot: '...' }) - await bridge.snapshot() - - const snapshotCall = execFileMock.mock.calls.find((c: unknown[]) => - (c[1] as string[]).includes('snapshot') - ) - expect((snapshotCall![1] as string[]).at(-1)).toBe('--json') - }) - - it('translates success response to result', async () => { - succeedWith({ snapshot: 'tree output' }) - const result = await bridge.snapshot() - expect(result).toEqual({ browserPageId: 'tab-1', snapshot: 'tree output' }) - }) - - it('translates error response to BrowserError', async () => { - failWith('Element not found') - await expect(bridge.click('@e1')).rejects.toThrow('Element not found') - }) - it('keeps CDP discovery failures generic while the tab session is still live', async () => { failWith(CDP_DISCOVERY_FAILURE) await expect(bridge.snapshot()).rejects.toMatchObject({ @@ -218,102 +159,6 @@ describe('AgentBrowserBridge', () => { expect(args).toContain('@e3') }) - it('serializes concurrent commands per session', async () => { - const commandCalls: string[][] = [] - - execFileMock.mockImplementation( - (_bin: string, args: string[], _opts: unknown, cb: ExecFileCallback) => { - commandCalls.push(args) - cb(null, JSON.stringify({ success: true, data: { ok: true } }), '') - } - ) - - const [r1, r2] = await Promise.all([bridge.snapshot(), bridge.click('@e1')]) - expect(r1).toEqual({ browserPageId: 'tab-1', ok: true }) - expect(r2).toEqual({ ok: true }) - const snapshotIdx = commandCalls.findIndex((a) => a.includes('snapshot')) - const clickIdx = commandCalls.findIndex((a) => a.includes('click')) - expect(snapshotIdx).toBeLessThan(clickIdx) - }) - - it('builds cookie set args with all options', async () => { - succeedWith({ success: true }) - await bridge.cookieSet({ - name: 'sid', - value: 'abc', - domain: '.example.com', - path: '/', - secure: true, - httpOnly: true, - sameSite: 'Lax', - expires: 1700000000 - }) - - const args = execFileMock.mock.calls.at(-1)![1] as string[] - expect(args).toContain('cookies') - expect(args).toContain('set') - expect(args).toContain('sid') - expect(args).toContain('abc') - expect(args).toContain('--domain') - expect(args).toContain('.example.com') - expect(args).toContain('--path') - expect(args).toContain('/') - expect(args).toContain('--secure') - expect(args).toContain('--httpOnly') - expect(args).toContain('--sameSite') - expect(args).toContain('Lax') - expect(args).toContain('--expires') - expect(args).toContain('1700000000') - }) - - it('applies viewport emulation through CDP so mobile mode is preserved', async () => { - const wc = mockWebContents(100) - webContentsFromIdMock.mockReturnValue(wc) - - await bridge.setViewport(375, 812, 2, true) - - expect(wc.debugger.sendCommand).toHaveBeenCalledWith('Emulation.setDeviceMetricsOverride', { - width: 375, - height: 812, - deviceScaleFactor: 2, - mobile: true - }) - expect(wc.debugger.sendCommand).toHaveBeenCalledWith('Emulation.setVisibleSize', { - width: 375, - height: 812 - }) - const viewportCall = execFileMock.mock.calls.find((call: unknown[]) => - (call[1] as string[]).includes('viewport') - ) - expect(viewportCall).toBeUndefined() - }) - - it('normalizes selector wait state=visible to the default supported semantics', async () => { - succeedWith({ selector: 'h1', waited: 'selector' }) - - await bridge.wait({ selector: 'h1', state: 'visible' }) - - const args = execFileMock.mock.calls.at(-1)![1] as string[] - expect(args).toContain('wait') - expect(args).toContain('h1') - expect(args).not.toContain('--state') - }) - - it('enforces conditional wait timeouts at the bridge layer', async () => { - succeedWith({ selector: '#ready', waited: 'selector' }) - - await bridge.wait({ selector: '#ready', timeout: 1200 }) - - const args = execFileMock.mock.calls.at(-1)![1] as string[] - const options = execFileMock.mock.calls.at(-1)![2] as { timeout?: number; env?: unknown } - expect(args).toContain('wait') - expect(args).toContain('#ready') - expect(options.timeout).toBe(2200) - const env = options.env as NodeJS.ProcessEnv - expect(env.PATH).toBe(process.env.PATH) - expect(env.AGENT_BROWSER_IDLE_TIMEOUT_MS).toBe(String(AGENT_BROWSER_IDLE_TIMEOUT_MS)) - }) - it('returns browser_timeout for timed conditional waits without recycling the session', async () => { const killedError = Object.assign(new Error('timeout'), { killed: true }) execFileMock.mockImplementation( diff --git a/src/main/browser/agent-browser-bridge-keypress-input.test.ts b/src/main/browser/agent-browser-bridge-keypress-input.test.ts index e709c2029f5..6620ffe234d 100644 --- a/src/main/browser/agent-browser-bridge-keypress-input.test.ts +++ b/src/main/browser/agent-browser-bridge-keypress-input.test.ts @@ -218,14 +218,6 @@ describe('AgentBrowserBridge keypress input', () => { expect(keyEventCalls(wc)[1]).toMatchObject({ type: 'keyUp', windowsVirtualKeyCode: 229 }) }) - it('keeps the helper for a surrogate-pair character', async () => { - succeedWith({ pressed: '👍' }) - - await expect(bridge.keypress('👍', undefined, 'tab-1')).resolves.toEqual({ pressed: '👍' }) - - expect(keyEventCalls(wc)).toHaveLength(0) - }) - it('falls back to agent-browser for a key name the table cannot express', async () => { succeedWith({ pressed: 'MediaPlayPause' }) diff --git a/src/main/browser/agent-browser-bridge-mouse-input.test.ts b/src/main/browser/agent-browser-bridge-mouse-input.test.ts index 332d12d7460..9b3ebbc735b 100644 --- a/src/main/browser/agent-browser-bridge-mouse-input.test.ts +++ b/src/main/browser/agent-browser-bridge-mouse-input.test.ts @@ -198,17 +198,4 @@ describe('AgentBrowserBridge', () => { expect(mouseCalls[0]?.[1]).toMatchObject({ type: 'mousePressed', x: 12, y: 34, modifiers: 4 }) expect(mouseCalls[1]?.[1]).toMatchObject({ type: 'mouseReleased', x: 12, y: 34, modifiers: 4 }) }) - - it('drops empty command queues after direct CDP commands finish', async () => { - const wc = mockWebContents(100) - wc.debugger.sendCommand.mockResolvedValue({}) - webContentsFromIdMock.mockReturnValue(wc) - - await bridge.mouseClick(10, 20, 'right', undefined, 'tab-1') - - expect( - (bridge as unknown as { commandQueues: Map }).commandQueues.size - ).toBe(0) - expect((bridge as unknown as { processingQueues: Set }).processingQueues.size).toBe(0) - }) }) diff --git a/src/main/browser/agent-browser-bridge-navigation.test.ts b/src/main/browser/agent-browser-bridge-navigation.test.ts index 32d3aa1bbbd..93b88097792 100644 --- a/src/main/browser/agent-browser-bridge-navigation.test.ts +++ b/src/main/browser/agent-browser-bridge-navigation.test.ts @@ -82,65 +82,6 @@ describe('AgentBrowserBridge', () => { bridge.setActiveTab(100) }) - it('clears reload fallback timer after the load event settles', async () => { - vi.useFakeTimers() - try { - succeedWith(null) - const wc = { - ...mockWebContents(100, 'https://reloaded.example', 'Reloaded'), - reload: vi.fn(), - on: vi.fn(), - removeListener: vi.fn() - } - webContentsFromIdMock.mockReturnValue(wc) - - const result = bridge.reload() - - await vi.waitFor(() => { - expect(wc.on).toHaveBeenCalledWith('did-finish-load', expect.any(Function)) - }) - - const finishListener = wc.on.mock.calls.find( - ([event]) => event === 'did-finish-load' - )?.[1] as (() => void) | undefined - const failListener = wc.on.mock.calls.find(([event]) => event === 'did-fail-load')?.[1] as - | (() => void) - | undefined - expect(finishListener).toBeDefined() - expect(failListener).toBeDefined() - expect(vi.getTimerCount()).toBe(1) - - finishListener!() - - await expect(result).resolves.toEqual({ - url: 'https://reloaded.example', - title: 'Reloaded' - }) - expect(wc.removeListener).toHaveBeenCalledWith('did-finish-load', finishListener) - expect(wc.removeListener).toHaveBeenCalledWith('did-fail-load', failListener) - expect(vi.getTimerCount()).toBe(0) - - await vi.advanceTimersByTimeAsync(10_000) - expect(wc.removeListener).toHaveBeenCalledTimes(2) - } finally { - vi.useRealTimers() - } - }) - - it('navigates the registered webContents without spawning agent-browser', async () => { - const wc = mockWebContents(100, 'https://example.com/start', 'Example') - webContentsFromIdMock.mockReturnValue(wc) - - await expect(bridge.goto('https://example.com/next')).resolves.toEqual({ - url: 'https://example.com/next', - title: 'Example' - }) - - expect(wc.loadURL).toHaveBeenCalledWith('https://example.com/next') - expect(wc.isLoading).not.toHaveBeenCalled() - expect(execFileMock).not.toHaveBeenCalled() - }) - it('preserves scheme-less navigation semantics at the direct WebContents boundary', async () => { const wc = mockWebContents(100) webContentsFromIdMock.mockReturnValue(wc) diff --git a/src/main/browser/agent-browser-bridge-pointer-input.test.ts b/src/main/browser/agent-browser-bridge-pointer-input.test.ts index 371d2ed30e0..996dca73b55 100644 --- a/src/main/browser/agent-browser-bridge-pointer-input.test.ts +++ b/src/main/browser/agent-browser-bridge-pointer-input.test.ts @@ -101,20 +101,6 @@ describe('AgentBrowserBridge coordinate pointer input', () => { webContentsFromIdMock.mockReturnValue(wc) }) - it('dispatches move, down, up and wheel over CDP without spawning the helper', async () => { - await expect(bridge.mouseMove(10, 20)).resolves.toEqual({ moved: true }) - await expect(bridge.mouseDown('left')).resolves.toEqual({ pressed: true }) - await expect(bridge.mouseUp('left')).resolves.toEqual({ released: true }) - await expect(bridge.mouseWheel(120, 30)).resolves.toEqual({ - scrolled: true, - deltaX: 30, - deltaY: 120 - }) - - expect(execFileMock).not.toHaveBeenCalled() - expect(dispatched()).toHaveLength(4) - }) - it('sends CDP payloads matching a real pointer press and release', async () => { await bridge.mouseMove(10, 20) await bridge.mouseDown('left') @@ -343,14 +329,6 @@ describe('AgentBrowserBridge coordinate pointer input', () => { expect(dispatched()).toHaveLength(0) }) - it('reports a dispatch failure as browser_error on the call that failed', async () => { - wc.debugger.sendCommand.mockRejectedValueOnce(new Error('boom')) - - await expect(bridge.mouseMove(10, 20)).rejects.toMatchObject({ - code: 'browser_error' - }) - }) - it('reports a page that dies mid-dispatch as browser_tab_not_found', async () => { wc.debugger.sendCommand.mockImplementation(async () => { webContentsFromIdMock.mockReturnValue(null) @@ -415,12 +393,6 @@ describe('AgentBrowserBridge coordinate pointer input', () => { expect(wc.debugger.detach).not.toHaveBeenCalled() }) - it('focuses an offscreen page on press, as mouseClick does', async () => { - await bridge.mouseDown('left') - - expect(wc.focus).toHaveBeenCalled() - }) - it('guards the user’s focus instead of focusing an embedded guest on press', async () => { const release = vi.fn() const guardUserFocusForAgentInput = vi.fn(async () => release) diff --git a/src/main/browser/agent-browser-bridge-raw-process-retention.test.ts b/src/main/browser/agent-browser-bridge-raw-process-retention.test.ts index 014e0f392a2..3bd2c98bcb0 100644 --- a/src/main/browser/agent-browser-bridge-raw-process-retention.test.ts +++ b/src/main/browser/agent-browser-bridge-raw-process-retention.test.ts @@ -21,7 +21,6 @@ vi.mock('node:child_process', async (importOriginal) => { import { AgentBrowserBridge } from './agent-browser-bridge' import { mockBrowserManager } from './agent-browser-bridge-test-harness' -import { AGENT_BROWSER_EXIT_DRAIN_MS } from './agent-browser-bridge-types' let bridge: AgentBrowserBridge @@ -71,47 +70,3 @@ it('releases the completed helper before the fallback deadline while its bridge expect(child.deref()).toBeUndefined() expect(await run("process.stdout.write('next')")).toBe('next') }) - -it.each(['error', 'cancel'] as const)( - 'retires the unused drain timer after %s', - async (outcome) => { - const timers = vi.spyOn(globalThis, 'setTimeout') - const clear = vi.spyOn(globalThis, 'clearTimeout') - const spawned = vi.mocked(childProcess.execFile) - const pending = run( - outcome === 'error' - ? "process.stderr.write('fixture-error');process.exitCode=1" - : 'setInterval(() => {}, 1000)' - ) - if (outcome === 'cancel') { - const result = spawned.mock.results[0] - if (result?.type !== 'return') { - throw new Error('Expected a real helper process') - } - bridge['cancelledProcesses'].add(result.value) - result.value.kill() - } - await expect(pending).rejects.toThrow(outcome === 'error' ? 'fixture-error' : 'Tab was closed') - const completed = spawned.mock.results[0] - if (completed?.type !== 'return') { - throw new Error('Expected a real helper process') - } - expect(bridge['cancelledProcesses'].has(completed.value)).toBe(false) - const drainIndex = timers.mock.calls.findIndex( - ([, delay]) => delay === AGENT_BROWSER_EXIT_DRAIN_MS - ) - expect(drainIndex).toBeGreaterThanOrEqual(0) - const drain = timers.mock.results[drainIndex] - if (drain?.type !== 'return') { - throw new Error('Expected a scheduled drain timer') - } - expect(clear).toHaveBeenCalledWith(drain.value) - } -) - -it('preserves spawn failure without scheduling an exit drain', async () => { - Object.defineProperty(bridge, 'agentBrowserBin', { value: join(userDataPath.current, 'missing') }) - const timers = vi.spyOn(globalThis, 'setTimeout') - await expect(run('')).rejects.toThrow('ENOENT') - expect(timers.mock.calls.some(([, delay]) => delay === AGENT_BROWSER_EXIT_DRAIN_MS)).toBe(false) -}) diff --git a/src/main/browser/agent-browser-bridge-screenshot-format.test.ts b/src/main/browser/agent-browser-bridge-screenshot-format.test.ts deleted file mode 100644 index 7aee1ee311e..00000000000 --- a/src/main/browser/agent-browser-bridge-screenshot-format.test.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest' - -const { execFileMock, webContentsFromIdMock, existsSyncMock, readFileSyncMock, stdinWrites } = - vi.hoisted(() => ({ - execFileMock: vi.fn(), - webContentsFromIdMock: vi.fn(), - existsSyncMock: vi.fn(() => false), - readFileSyncMock: vi.fn(() => Buffer.from('')), - stdinWrites: new Array() - })) - -vi.mock('child_process', () => ({ execFile: execFileMock })) -vi.mock('fs', () => ({ - existsSync: existsSyncMock, - readFileSync: readFileSyncMock, - accessSync: vi.fn(), - chmodSync: vi.fn(), - constants: { X_OK: 1 } -})) -vi.mock('os', () => ({ platform: () => 'darwin', arch: () => 'arm64' })) -vi.mock('electron', () => ({ - app: { getPath: vi.fn(() => '/app'), getAppPath: vi.fn(() => '/project'), isPackaged: false }, - webContents: { fromId: webContentsFromIdMock } -})) -const { CdpWsProxyMock } = vi.hoisted(() => { - class MockCdpWsProxy { - static instances: unknown[] = [] - start = vi.fn(async () => 'ws://127.0.0.1:9222') - stop = vi.fn(async () => {}) - getPort = vi.fn(() => 9222) - - constructor(_wc: unknown) { - MockCdpWsProxy.instances.push(this) - } - } - return { CdpWsProxyMock: MockCdpWsProxy } -}) - -vi.mock('./cdp-ws-proxy', () => ({ CdpWsProxy: CdpWsProxyMock })) -vi.mock('./cdp-bridge', () => ({ - BrowserError: class BrowserError extends Error { - code: string - constructor(code: string, message: string) { - super(message) - this.code = code - } - } -})) - -import { AgentBrowserBridge } from './agent-browser-bridge' -import { - createSucceedWith, - mockBrowserManager, - overrideBridgeWebContentsLookup, - resetAgentBrowserBridgeMocks -} from './agent-browser-bridge-test-harness' - -overrideBridgeWebContentsLookup(AgentBrowserBridge.prototype, webContentsFromIdMock) - -const succeedWith = createSucceedWith(execFileMock, stdinWrites) - -function isStringArray(value: unknown): value is string[] { - return Array.isArray(value) && value.every((item) => typeof item === 'string') -} - -function screenshotArgs(): string[] { - const screenshotCall = execFileMock.mock.calls.find( - (call) => Array.isArray(call) && isStringArray(call[1]) && call[1].includes('screenshot') - ) - if (!screenshotCall || !isStringArray(screenshotCall[1])) { - throw new Error('Expected agent-browser screenshot arguments') - } - return screenshotCall[1] -} - -describe('AgentBrowserBridge viewport screenshot formats', () => { - let bridge: AgentBrowserBridge - - beforeEach(() => { - resetAgentBrowserBridgeMocks({ - webContentsFromIdMock, - existsSyncMock, - readFileSyncMock, - stdinWrites, - cdpWsProxyInstances: CdpWsProxyMock.instances - }) - bridge = new AgentBrowserBridge(mockBrowserManager()) - bridge.setActiveTab(100) - }) - - it('forwards JPEG encoding and returns matching JPEG bytes and metadata', async () => { - const jpeg = Buffer.from([0xff, 0xd8, 0xff, 0xe0]) - existsSyncMock.mockReturnValue(true) - readFileSyncMock.mockReturnValue(jpeg) - succeedWith({ path: '/tmp/capture.jpeg' }) - - const result = await bridge.screenshot('jpeg') - - expect(result).toEqual({ - data: jpeg.toString('base64'), - format: 'jpeg' - }) - expect(Buffer.from(result.data, 'base64')).toEqual(jpeg) - - expect(screenshotArgs()).toEqual( - expect.arrayContaining(['screenshot', '--screenshot-format', 'jpeg']) - ) - expect(screenshotArgs().slice(-4)).toEqual([ - 'screenshot', - '--screenshot-format', - 'jpeg', - '--json' - ]) - expect(screenshotArgs()).not.toContain('--screenshot-quality') - }) - - it.each([ - ['png', 'explicit PNG'], - [undefined, 'an omitted format'], - ['webp', 'an unsupported format'] - ])('normalizes %s to PNG for %s', async (requestedFormat, _description) => { - const png = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]) - existsSyncMock.mockReturnValue(true) - readFileSyncMock.mockReturnValue(png) - succeedWith({ path: '/tmp/capture.png' }) - - const result = await bridge.screenshot(requestedFormat) - - expect(result).toEqual({ - data: png.toString('base64'), - format: 'png' - }) - expect(Buffer.from(result.data, 'base64')).toEqual(png) - - expect(screenshotArgs().slice(-4)).toEqual([ - 'screenshot', - '--screenshot-format', - 'png', - '--json' - ]) - }) -}) diff --git a/src/main/browser/agent-browser-bridge-session-lifecycle.test.ts b/src/main/browser/agent-browser-bridge-session-lifecycle.test.ts index b609e5e9f03..81ee2de9e7b 100644 --- a/src/main/browser/agent-browser-bridge-session-lifecycle.test.ts +++ b/src/main/browser/agent-browser-bridge-session-lifecycle.test.ts @@ -155,44 +155,6 @@ describe('AgentBrowserBridge', () => { } }) - it('destroys session after 3 consecutive timeouts', async () => { - const killedError = Object.assign(new Error('timeout'), { killed: true }) - - execFileMock.mockImplementation( - (_bin: string, args: string[], _opts: unknown, cb: ExecFileCallback) => { - if (args.includes('close')) { - cb(null, JSON.stringify({ success: true, data: null }), '') - return - } - cb(killedError, '', '') - } - ) - - for (let i = 0; i < 3; i++) { - await expect(bridge.snapshot()).rejects.toThrow('timed out') - } - - succeedWith({ snapshot: 'fresh' }) - await bridge.snapshot() - - const lastArgs = execFileMock.mock.calls.at(-1)![1] as string[] - expect(lastArgs).toContain('--cdp') - }) - - it('bounds owner cleanup independently from command execution timeouts', async () => { - succeedWith({ snapshot: 'initial' }) - await bridge.snapshot() - execFileMock.mockClear() - - succeedWith(null) - await bridge.onPageClosed('tab-1') - - const closeCall = execFileMock.mock.calls.find((call: unknown[]) => - (call[1] as string[]).includes('close') - ) - expect(closeCall?.[2]).toMatchObject({ timeout: 5_000 }) - }) - it('waits for pending session destruction before recreating the same session', async () => { succeedWith({ snapshot: 'initial' }) await bridge.snapshot() @@ -496,33 +458,6 @@ describe('AgentBrowserBridge', () => { expect(routeCalls[0]).toContain('--cdp') }) - it('clears pending intercept restore state when a swapped tab closes before reuse', async () => { - const tabs = new Map([['tab-1', 100]]) - const mgr = mockBrowserManager(tabs) - const b = new AgentBrowserBridge(mgr) - b.setActiveTab(100) - - succeedWith({ ok: true }) - await b.interceptEnable(['https://old.example/**']) - - tabs.set('tab-1', 200) - webContentsFromIdMock.mockReturnValue(mockWebContents(200)) - succeedWith(null) - await b.onProcessSwap('tab-1', 200) - - expect( - (b as unknown as { pendingInterceptRestore: Map }).pendingInterceptRestore - .size - ).toBe(1) - - await b.onTabClosed(200) - - expect( - (b as unknown as { pendingInterceptRestore: Map }).pendingInterceptRestore - .size - ).toBe(0) - }) - it('replays intercept routes after the daemon idles out', async () => { succeedWith({ ok: true }) await bridge.interceptEnable(['https://api.example/**']) @@ -620,42 +555,4 @@ describe('AgentBrowserBridge', () => { expect(peakRetirements).toBe(4) }) - - it('bounds every teardown close well inside the quit barrier', async () => { - succeedWith({ snapshot: 'tree' }) - await bridge.snapshot() - - succeedWith(null) - await bridge.destroyAllSessions() - - const closeCall = execFileMock.mock.calls.findLast((c: unknown[]) => - (c[1] as string[]).includes('close') - ) - expect((closeCall![2] as { timeout: number }).timeout).toBeLessThanOrEqual(5_000) - }) - - it('closes a session still being created when everything is torn down', async () => { - let releaseProxyStart: (() => void) | undefined - CdpWsProxyMock.mockImplementationOnce(function (this: Record) { - this.start = vi.fn( - () => - new Promise((resolve) => { - releaseProxyStart = () => resolve('ws://127.0.0.1:9222') - }) - ) - this.stop = vi.fn(async () => {}) - this.getPort = vi.fn(() => 9222) - }) - - succeedWith({ snapshot: 'tree' }) - const inFlight = bridge.snapshot() - await vi.waitFor(() => expect(releaseProxyStart).toBeDefined()) - - const closesBeforeTeardown = closeCallCount() - const teardown = bridge.destroyAllSessions() - releaseProxyStart!() - await Promise.allSettled([inFlight, teardown]) - - expect(closeCallCount()).toBeGreaterThan(closesBeforeTeardown) - }) }) diff --git a/src/main/browser/agent-browser-bridge-tab-routing.test.ts b/src/main/browser/agent-browser-bridge-tab-routing.test.ts index 28e1c6a6f62..069ce26bc0b 100644 --- a/src/main/browser/agent-browser-bridge-tab-routing.test.ts +++ b/src/main/browser/agent-browser-bridge-tab-routing.test.ts @@ -217,66 +217,6 @@ describe('AgentBrowserBridge', () => { await expect(b.snapshot()).rejects.toThrow('No browser tab open') }) - it('clears activeWebContentsId on tab close', async () => { - succeedWith({ snapshot: 'tree' }) - await bridge.snapshot() - - await bridge.onTabClosed(100) - expect(bridge.getActiveWebContentsId()).toBeNull() - }) - - it('resolves repeated guest lookups without enumerating registered tabs', () => { - const tabCount = 1_000 - const tabs = new Map() - const tabIdByWebContentsId = new Map() - for (let index = 0; index < tabCount; index += 1) { - const tabId = `tab-${index}` - const webContentsId = 20_000 + index - tabs.set(tabId, webContentsId) - tabIdByWebContentsId.set(webContentsId, tabId) - } - - const getRegisteredTabs = vi.fn(() => { - throw new Error('unexpected registered-tab enumeration') - }) - const getTabIdForWebContentsId = vi.fn( - (webContentsId: number) => tabIdByWebContentsId.get(webContentsId) ?? null - ) - const b = new AgentBrowserBridge( - mockBrowserManager(tabs, new Map(), { - getWebContentsIdByTabId: getRegisteredTabs, - getTabIdForWebContentsId - }) - ) - const resolveTabIdSafe = ( - b as unknown as { resolveTabIdSafe: (webContentsId: number) => string | null } - ).resolveTabIdSafe.bind(b) - const targetWebContentsId = 20_000 + tabCount - 1 - - for (let lookup = 0; lookup < tabCount; lookup += 1) { - expect(resolveTabIdSafe(targetWebContentsId)).toBe(`tab-${tabCount - 1}`) - } - - expect(getTabIdForWebContentsId).toHaveBeenCalledTimes(tabCount) - expect(getRegisteredTabs).not.toHaveBeenCalled() - expect(resolveTabIdSafe(99_999)).toBeNull() - }) - - it('closes the named agent-browser session when a tab closes', async () => { - succeedWith({ snapshot: 'tree' }) - await bridge.snapshot() - - execFileMock.mockClear() - succeedWith(null) - await bridge.onTabClosed(100) - - const closeCall = execFileMock.mock.calls.find((call: unknown[]) => - (call[1] as string[]).includes('close') - ) - expect(closeCall).toBeTruthy() - expect(closeCall![1]).toEqual(['--session', 'orca-tab-tab-1', 'close']) - }) - it('repairs per-worktree active routing when the active tab closes', async () => { const tabs = new Map([ ['tab-a', 1], @@ -313,40 +253,6 @@ describe('AgentBrowserBridge', () => { expect(b.tabList('wt-1').tabs).toMatchObject([{ browserPageId: 'tab-a', active: true }]) }) - it('switches active tab and returns switched index', async () => { - const tabs = new Map([ - ['tab-a', 1], - ['tab-b', 2] - ]) - const wc1 = mockWebContents(1) - const wc2 = mockWebContents(2) - webContentsFromIdMock.mockImplementation((id: number) => (id === 1 ? wc1 : wc2)) - - const b = new AgentBrowserBridge(mockBrowserManager(tabs)) - b.setActiveTab(1) - - const result = await b.tabSwitch(1) - expect(result).toEqual({ switched: 1, browserPageId: 'tab-b' }) - expect(b.getActiveWebContentsId()).toBe(2) - }) - - it('switches tabs by explicit browser page id', async () => { - const tabs = new Map([ - ['tab-a', 1], - ['tab-b', 2] - ]) - const wc1 = mockWebContents(1) - const wc2 = mockWebContents(2) - webContentsFromIdMock.mockImplementation((id: number) => (id === 1 ? wc1 : wc2)) - - const b = new AgentBrowserBridge(mockBrowserManager(tabs)) - b.setActiveTab(1) - - const result = await b.tabSwitch(undefined, undefined, 'tab-b') - expect(result).toEqual({ switched: 1, browserPageId: 'tab-b' }) - expect(b.getActiveWebContentsId()).toBe(2) - }) - it('updates the owning worktree active tab when switching by browser page id', async () => { const tabs = new Map([ ['tab-a', 1], diff --git a/src/main/browser/agent-browser-bridge-text-input.test.ts b/src/main/browser/agent-browser-bridge-text-input.test.ts index 56d863ddaaa..29cf24eba73 100644 --- a/src/main/browser/agent-browser-bridge-text-input.test.ts +++ b/src/main/browser/agent-browser-bridge-text-input.test.ts @@ -318,19 +318,6 @@ describe('AgentBrowserBridge', () => { expect(environment.editor.textContent).toBe(`${firstChunk}tail`) }) - it('uses target-aware agent-browser fill when clearing a non-rich target', async () => { - succeedWith({ filled: '@disabled' }) - - const result = await bridge.clear('@disabled') - - const commandArgs = execFileMock.mock.calls.map((call: unknown[]) => call[1] as string[]) - expect(commandArgs.some((args) => args.includes('eval'))).toBe(false) - expect(commandArgs.some((args) => args.includes('fill') && args.includes('@disabled'))).toBe( - true - ) - expect(result).toEqual({ cleared: '@disabled' }) - }) - it('routes focused spinbutton wrappers to editable descendants before filling', async () => { succeedWith({ ok: true }) @@ -437,24 +424,6 @@ describe('AgentBrowserBridge', () => { expect(wrapper.events).toHaveLength(0) }) - it('keeps plain focused inputs as fill targets', async () => { - succeedWith({ ok: true }) - - await bridge.fill('@input', '200') - - const expressions = stdinWrites - - const input = createFillEvalNode({ tagName: 'INPUT' }) - - runFillEvalExpressions(expressions, { - activeElement: input.node, - getElementById: () => null - }) - - expect(input.value).toBe('200') - expect(input.events.map((event) => event.type)).toEqual(['input', 'change']) - }) - it('fills large plain fields with one stdin edit and one event pair', async () => { const text = `${'é\n'.repeat(512 * 1024)}tail'\\` succeedWith({ ok: true }) diff --git a/src/main/browser/agent-browser-orphan-sweep.test.ts b/src/main/browser/agent-browser-orphan-sweep.test.ts index 7601ada6c02..8e70b44b9cd 100644 --- a/src/main/browser/agent-browser-orphan-sweep.test.ts +++ b/src/main/browser/agent-browser-orphan-sweep.test.ts @@ -41,18 +41,6 @@ describe('agent-browser orphan sweep', () => { runProcessMock.mockReset() }) - it('closes tab daemons left by a previous run', async () => { - respond(['orca-tab-aaa', 'orca-tab-bbb']) - - const closed = await sweepOrphanedAgentBrowserSessions({ binaryPath: BIN, ...SCOPED }) - - expect(closed).toEqual(['orca-tab-aaa', 'orca-tab-bbb']) - expect(closedArgs()).toEqual([ - ['--session', 'orca-tab-aaa', 'close'], - ['--session', 'orca-tab-bbb', 'close'] - ]) - }) - it('never closes a daemon outside Orca tab naming', async () => { respond(['default', 'agent1', 'orca-orcad-deadbeef', 'orca-tab-aaa']) @@ -104,14 +92,6 @@ describe('agent-browser orphan sweep', () => { expect(closedArgs()).toEqual([]) }) - it('survives a listing that never returns', async () => { - runProcessMock.mockRejectedValue(new Error('ENOENT')) - - await expect( - sweepOrphanedAgentBrowserSessions({ binaryPath: BIN, ...SCOPED }) - ).resolves.toEqual([]) - }) - it('keeps sweeping after one close fails', async () => { runProcessMock.mockImplementation((spec: Spec) => { if (spec.args?.[0] === 'session') { @@ -133,16 +113,6 @@ describe('agent-browser orphan sweep', () => { expect(closed).toEqual(['orca-tab-bbb']) }) - - it('bounds every child it starts', async () => { - respond(['orca-tab-aaa']) - - await sweepOrphanedAgentBrowserSessions({ binaryPath: BIN, ...SCOPED }) - - for (const call of runProcessMock.mock.calls) { - expect((call[0] as { timeoutMs?: number | null }).timeoutMs).toBeGreaterThan(0) - } - }) }) describe('sweep kill switch', () => { @@ -169,18 +139,4 @@ describe('sweep kill switch', () => { expect(closed).toEqual([]) expect(runProcessMock).not.toHaveBeenCalled() }) - - it('still sweeps when the flag holds any other value', async () => { - process.env.ORCA_DISABLE_AGENT_BROWSER_SWEEP = '0' - runProcessMock.mockClear() - runProcessMock.mockResolvedValue({ code: 0, stdout: '{"data":{"sessions":[]}}', stderr: '' }) - - await sweepOrphanedAgentBrowserSessions({ - binaryPath: BIN, - env: {}, - ownsSocketDirectory: true - }) - - expect(runProcessMock).toHaveBeenCalled() - }) }) diff --git a/src/shared/mobile-web-bundle/manifest-contract.test.ts b/src/shared/mobile-web-bundle/manifest-contract.test.ts index fd8dd2fadb3..350bb46b59d 100644 --- a/src/shared/mobile-web-bundle/manifest-contract.test.ts +++ b/src/shared/mobile-web-bundle/manifest-contract.test.ts @@ -11,7 +11,6 @@ import { MOBILE_WEB_BUNDLE_MAX_ROUTES, MOBILE_WEB_BUNDLE_MAX_TOTAL_BYTES, MOBILE_WEB_BUNDLE_SCHEMA_VERSION, - MOBILE_WEB_PAGE_VERSION, type MobileWebBundleAsset } from './manifest-contract' @@ -117,10 +116,6 @@ describe('the page version a manifest carries', () => { ).toBe(false) } }) - - it('is at least 1, so a manifest without one reads as older than every page that has one', () => { - expect(MOBILE_WEB_PAGE_VERSION).toBeGreaterThanOrEqual(1) - }) }) describe('the page routes a manifest declares', () => { @@ -352,31 +347,6 @@ describe('refinement short-circuit', () => { const withinCeiling = assetsTotalling(MOBILE_WEB_BUNDLE_MAX_ASSETS, 64) expect(issuePaths(manifestOf(withinCeiling, { buildId: WRONG_BUILD_ID }))).toEqual(['buildId']) }) - - it('does not hash an oversized asset list', () => { - const overCeiling = [ - ...assetsTotalling(MOBILE_WEB_BUNDLE_MAX_ASSETS, 64), - asset('assets/overflow.js', 0) - ] - const paths = issuePaths(manifestOf(overCeiling, { buildId: WRONG_BUILD_ID })) - expect(paths).toEqual(['assets']) - expect(paths).not.toContain('buildId') - }) - - it('does not hash once a cheaper invariant has failed', () => { - const twoAssets = [ENTRY, asset('assets/a.js', 10)] - expect(issuePaths(manifestOf(twoAssets, { totalBytes: 0, buildId: WRONG_BUILD_ID }))).toEqual([ - 'totalBytes' - ]) - expect( - issuePaths( - manifestOf(twoAssets, { - minCompatibleRuntimeProtocolVersion: 4, - buildId: WRONG_BUILD_ID - }) - ) - ).toEqual(['minCompatibleRuntimeProtocolVersion']) - }) }) describe('asset paths', () => { diff --git a/src/shared/orchestration-dispatch-refusal-contract.test.ts b/src/shared/orchestration-dispatch-refusal-contract.test.ts index f60371c73f5..c8dd92b4368 100644 --- a/src/shared/orchestration-dispatch-refusal-contract.test.ts +++ b/src/shared/orchestration-dispatch-refusal-contract.test.ts @@ -1,10 +1,8 @@ import { describe, expect, it } from 'vitest' import { buildInjectRejectionMessage, - taskNotFoundRefusal, taskNotStartableRefusal } from './orchestration-dispatch-refusal-contract' -import { TUI_AGENT_CONFIG } from './tui-agent-config' import { recognizeAgentProcess } from './agent-process-recognition' describe('buildInjectRejectionMessage', () => { @@ -15,13 +13,6 @@ describe('buildInjectRejectionMessage', () => { expect(message).toContain('no recognized agent detected') }) - it('names every agent Orca recognizes, including agy', () => { - expect(message).toMatch(/\bagy\b/) - for (const config of Object.values(TUI_AGENT_CONFIG)) { - expect(message).toContain(config.expectedProcess) - } - }) - it('lists only names detection actually resolves, deduped and sorted', () => { const listed = (/\(([^)]+)\)/.exec(message)?.[1] ?? '').split(', ') @@ -36,19 +27,6 @@ describe('buildInjectRejectionMessage', () => { // Pin published receipt literals independently so refactors cannot change builders and expectations together. describe('dispatch refusal receipts keep their published messages', () => { - it('leaves the message exactly as each call site supplies it', () => { - expect(taskNotFoundRefusal('Task not found: task_1', { taskId: 'task_1' }).message).toBe( - 'Task not found: task_1' - ) - expect( - taskNotStartableRefusal('Task task_1 is pending; only a ready Task can start.', { - taskId: 'task_1', - status: 'pending', - unmetDependencies: [] - }).message - ).toBe('Task task_1 is pending; only a ready Task can start.') - }) - it('tailors nextSteps to retry, dependency, occupancy, and terminal-status refusals', () => { const base = { taskId: 'task_1', status: 'failed', unmetDependencies: [] } expect(taskNotStartableRefusal('m', { ...base, retryOf: 'ctx_1' }).data.nextSteps[0]).toMatch( diff --git a/src/shared/orchestration-fleet-attention.test.ts b/src/shared/orchestration-fleet-attention.test.ts index d074c76d828..263c12839a1 100644 --- a/src/shared/orchestration-fleet-attention.test.ts +++ b/src/shared/orchestration-fleet-attention.test.ts @@ -50,28 +50,4 @@ describe('orchestration fleet attention', () => { expect(child.categories).toEqual([]) expect(root).toEqual({ categories: ['root_completion'], requiresAction: false }) }) - - it('measures a five-worker wave without choosing one alert policy', () => { - const wave = [ - { isRoot: true, outcome: 'succeeded' as const }, - { isRoot: false, outcome: 'succeeded' as const }, - { isRoot: false, outcome: 'in_progress' as const, pendingInput: true }, - { isRoot: false, outcome: 'failed' as const }, - { isRoot: false, outcome: 'in_progress' as const, interrupted: true } - ].map((facts) => - projectOrchestrationFleetAttention({ - ...facts, - liveness: { verdict: facts.outcome === 'in_progress' ? 'live' : 'exited' } - }) - ) - const counts = wave - .flatMap((entry) => entry.categories) - .reduce>( - (result, category) => ({ ...result, [category]: (result[category] ?? 0) + 1 }), - {} - ) - - expect(counts).toEqual({ root_completion: 1, input: 1, failure: 1, interruption: 1 }) - expect(wave.filter((entry) => entry.requiresAction)).toHaveLength(3) - }) }) diff --git a/src/shared/orchestration-task-display.test.ts b/src/shared/orchestration-task-display.test.ts index 92f7320d57e..7d3aed0e5ed 100644 --- a/src/shared/orchestration-task-display.test.ts +++ b/src/shared/orchestration-task-display.test.ts @@ -42,14 +42,4 @@ describe('buildOrchestrationTaskDisplayMetadata', () => { expect(metadata.displayName).toHaveLength(ORCHESTRATION_DISPLAY_NAME_MAX_LENGTH) expect(metadata.displayName.endsWith('...')).toBe(true) }) - - it('does not leave a dangling high surrogate when truncating', () => { - const metadata = buildOrchestrationTaskDisplayMetadata({ - spec: `x${'😀'.repeat(ORCHESTRATION_TASK_TITLE_MAX_LENGTH)}` - }) - const last = metadata.taskTitle.charCodeAt(metadata.taskTitle.length - 1) - - expect(metadata.taskTitle.length).toBeLessThanOrEqual(ORCHESTRATION_TASK_TITLE_MAX_LENGTH) - expect(last < 0xd800 || last > 0xdbff).toBe(true) - }) }) diff --git a/src/shared/osc-title-scan-tail-retention.test.ts b/src/shared/osc-title-scan-tail-retention.test.ts index ab1643b634e..53729678f38 100644 --- a/src/shared/osc-title-scan-tail-retention.test.ts +++ b/src/shared/osc-title-scan-tail-retention.test.ts @@ -1,49 +1,7 @@ -import { describe, expect, it, vi } from 'vitest' -import * as ownership from './own-retained-string' +import { describe, expect, it } from 'vitest' import { extractOscTitleScanTail } from './osc-title-scan-tail' -const INCOMPLETE_TITLE = '\x1b]2;Working on a terminal title' - describe('OSC title scan tail storage', () => { - it('routes every retained title tail through ownRetainedString', () => { - const own = vi.spyOn(ownership, 'ownRetainedString') - try { - const tail = extractOscTitleScanTail('x'.repeat(32 * 1024) + INCOMPLETE_TITLE) - expect(own).toHaveBeenCalledTimes(1) - expect(own).toHaveBeenLastCalledWith(INCOMPLETE_TITLE) - expect(tail).toBe(INCOMPLETE_TITLE) - - // Ownership is unconditional: a growing title is re-owned on every chunk. - let pending = tail - for (let index = 0; index < 8; index += 1) { - pending = extractOscTitleScanTail(pending + 'x'.repeat(512)) - } - expect(own).toHaveBeenCalledTimes(9) - expect(own).toHaveBeenLastCalledWith(pending) - - // An unrelated incomplete OSC still yields nothing to retain. - expect(extractOscTitleScanTail(`${'x'.repeat(32 * 1024)}\x1b]9999;incomplete`)).toBe('') - } finally { - own.mockRestore() - } - }) - - it.each([ - [16 * 1024, 256, INCOMPLETE_TITLE], - [64 * 1024, 64, INCOMPLETE_TITLE], - [1024 * 1024, 16, INCOMPLETE_TITLE], - [16 * 1024, 128, INCOMPLETE_TITLE + 'x'.repeat(16 * 1024)] - ])('keeps %i-character chunks with %i incomplete titles byte-exact', (size, count, title) => { - const tails: string[] = [] - for (let index = 0; index < count; index++) { - tails.push( - extractOscTitleScanTail(String.fromCharCode(65 + (index % 26)).repeat(size) + title) - ) - } - const expected = title.length <= 4096 ? title : title.slice(0, 4) + title.slice(-4092) - expect(tails).toEqual(Array.from({ length: count }, () => expected)) - }) - it.each(['0', '1', '2'])('preserves title %s introducer and exact UTF-16 at the cap', (code) => { const prefix = `\x1b]${code};` for (const length of [4095, 4096, 4097, 16 * 1024]) { diff --git a/src/shared/osc52-clipboard.test.ts b/src/shared/osc52-clipboard.test.ts index 7d30243f245..9ad30535646 100644 --- a/src/shared/osc52-clipboard.test.ts +++ b/src/shared/osc52-clipboard.test.ts @@ -368,28 +368,6 @@ describe('createOsc52OscHandler', () => { }) describe('resolveOsc52ClipboardGate', () => { - it('allows a live write once the setting is on', () => { - expect(resolveOsc52ClipboardGate({ settingEnabled: true, replaying: false })).toEqual({ - allowClipboardWrite: true, - shouldSurfaceBlockedWrite: false - }) - }) - - it('drops replayed writes so restore cannot clobber the clipboard', () => { - // Restore replays old PTY copies through the parser and could overwrite newer clipboard content (#10588). - expect(resolveOsc52ClipboardGate({ settingEnabled: true, replaying: true })).toEqual({ - allowClipboardWrite: false, - shouldSurfaceBlockedWrite: false - }) - }) - - it('surfaces the blocked toast only for a real opt-out', () => { - expect(resolveOsc52ClipboardGate({ settingEnabled: false, replaying: false })).toEqual({ - allowClipboardWrite: false, - shouldSurfaceBlockedWrite: true - }) - }) - it('stays quiet when a write races settings hydration', () => { // Pre-hydration reads look blocked despite default-on; consuming the once-per-session toast hides real refusals. for (const settingEnabled of [null, undefined]) { diff --git a/src/shared/paired-e2ee-device-key-auth.test.ts b/src/shared/paired-e2ee-device-key-auth.test.ts index cfab27ccf9e..542b20e308c 100644 --- a/src/shared/paired-e2ee-device-key-auth.test.ts +++ b/src/shared/paired-e2ee-device-key-auth.test.ts @@ -1,6 +1,6 @@ import nacl from 'tweetnacl' import { describe, expect, it } from 'vitest' -import { encodeDeviceProofMessage, verifyDeviceProof } from './device-key-proof' +import { verifyDeviceProof } from './device-key-proof' import { createMobileE2EEV2Fixture, MOBILE_E2EE_V2_VECTOR } from './mobile-e2ee-v2-fixtures' import { openMobileE2EEV2Frame } from './mobile-e2ee-v2-framing' import { createPairedE2EEClientHandshake } from './paired-e2ee-client-handshake' @@ -99,19 +99,4 @@ describe('paired E2EE device-key auth', () => { expect(early.transcriptHashB64()).toBeNull() await expect(early.sealAuth(keyCredential)).rejects.toThrow() }) - - it('signs the documented proof message', async () => { - const handshake = v2Handshake() - const seen: Uint8Array[] = [] - await handshake.sealAuth({ - ...keyCredential, - sign: async (message) => { - seen.push(message) - return nacl.sign.detached(message, key.secretKey) - } - }) - expect(seen[0]).toEqual( - encodeDeviceProofMessage('auth', Buffer.from(handshake.transcriptHashB64()!, 'base64')) - ) - }) }) diff --git a/src/shared/pairing-address-auto-selection.test.ts b/src/shared/pairing-address-auto-selection.test.ts index 79dc763e6e6..011bfda4fd2 100644 --- a/src/shared/pairing-address-auto-selection.test.ts +++ b/src/shared/pairing-address-auto-selection.test.ts @@ -70,15 +70,4 @@ describe('selectAutoAdvertisedPairingAddress', () => { ])('does not treat the route-backed near-match %s as a known host-local label', (name) => { expect(isVirtualBridgeInterface(name, true)).toBe(false) }) - - it.each([ - ['external', EXTERNAL_SWITCH, false], - ['Default Switch', DEFAULT_SWITCH, true], - ['WSL', WSL_SWITCH, true], - ['physical', PHYSICAL_LAN, false], - ['host-local', HOST_LOCAL_BRIDGE, true], - ['ambiguous', AMBIGUOUS_SWITCH, true] - ] as const)('classifies the %s fixture', (_label, fixture, expected) => { - expect(isVirtualBridgeInterface(fixture.name, fixture.hasDefaultRoute)).toBe(expected) - }) }) diff --git a/src/shared/pairing-local-ui-fields.test.ts b/src/shared/pairing-local-ui-fields.test.ts index b12b5177ae7..a931db05ca5 100644 --- a/src/shared/pairing-local-ui-fields.test.ts +++ b/src/shared/pairing-local-ui-fields.test.ts @@ -1,30 +1,7 @@ import { describe, expect, it } from 'vitest' -import { omitPairingLocalUiFields, PAIRING_LOCAL_UI_FIELDS } from './pairing-local-ui-fields' +import { omitPairingLocalUiFields } from './pairing-local-ui-fields' describe('pairing-local UI fields', () => { - // Host RPC and web-preload seams are spread out; a census catches new fields without complete boundary wiring. - it('census: the set is exactly the fields no pairing may exchange', () => { - expect([...PAIRING_LOCAL_UI_FIELDS]).toEqual([ - 'automationHostFilter', - 'hideWorkspacesFromOtherDevices', - 'manualRepoOrder', - 'workspaceHostOrder', - 'sidebarOpen', - 'agentsVisibleHostIds', - 'agentsFilterRepoIds', - 'agentsHideWorkspacesFromOtherDevices', - 'agentsHideAutomationGeneratedWorkspaces', - 'agentsHideCliCreatedWorkspaces', - 'agentsShowChildAgents', - 'agentsCompactMode', - 'agentsShowSearch', - 'agentsReadFilter', - 'agentsGroupBy', - 'activityClearedAtByPaneKey', - 'manuallyUnreadTurnsByPaneKey' - ]) - }) - it('omits every member and keeps everything else', () => { const state = { hideWorkspacesFromOtherDevices: true, diff --git a/src/shared/pairing.test.ts b/src/shared/pairing.test.ts index 893e9ec60ca..295827262e8 100644 --- a/src/shared/pairing.test.ts +++ b/src/shared/pairing.test.ts @@ -100,12 +100,6 @@ describe('pairing offer', () => { expect(decodePairingOffer(encodePairingOffer(proxiedOffer))).toEqual(proxiedOffer) }) - it('encoded URL uses base64url (no +, /, or = characters)', () => { - const url = encodePairingOffer(offer) - const code = new URLSearchParams(url.slice(url.indexOf('?') + 1)).get('code')! - expect(code).not.toMatch(/[+/=]/) - }) - it('rejects URLs with wrong scheme', () => { expect(() => decodePairingOffer('https://example.com#abc')).toThrow('Invalid pairing URL') }) @@ -165,11 +159,6 @@ describe('parsePairingCode', () => { publicKeyB64: 'pubkey-xyz' } - it('parses a full orca://pair# URL', () => { - const url = encodePairingOffer(offer) - expect(parsePairingCode(url)).toEqual(offer) - }) - it('parses a bare base64url payload (without scheme prefix)', () => { const url = encodePairingOffer(offer) const base64url = new URLSearchParams(url.slice(url.indexOf('?') + 1)).get('code')! diff --git a/src/shared/pane-agent-identity-adapter.test.ts b/src/shared/pane-agent-identity-adapter.test.ts index 47399ab05b7..1f6d64e1ca4 100644 --- a/src/shared/pane-agent-identity-adapter.test.ts +++ b/src/shared/pane-agent-identity-adapter.test.ts @@ -37,12 +37,6 @@ describe('per-pane coverage gate', () => { 'uncovered' ) }) - - it('is computed from evidence, never from a platform or remote flag', () => { - // No platform/isRemote input: host-proven hooks must resolve identically on every execution host. - const identity = resolveCanonicalPaneAgentIdentity({ hookAgent: 'claude', hookIsLive: true }) - expect(identity).toMatchObject({ agent: 'claude', source: 'live-hook', coverage: 'covered' }) - }) }) describe('process rung requires a host-stamped proof', () => { @@ -158,21 +152,6 @@ describe('canonical ladder inside the covered lane', () => { expect(identity).toMatchObject({ agent: 'claude', source: 'launch', titleOnly: false }) }) - it('sibling evidence needs the explicit tab-scope opt-in', () => { - const withoutOptIn = resolveCanonicalPaneAgentIdentity({ - launchAgent: 'claude', - siblingAgent: 'codex' - }) - expect(withoutOptIn.agent).toBe('claude') - const optedIn = resolveCanonicalPaneAgentIdentity({ - hookAgent: 'claude', - hookIsLive: true, - siblingAgent: 'codex', - allowSibling: true - }) - expect(optedIn).toMatchObject({ agent: 'claude', source: 'live-hook' }) - }) - it('surfaces ambiguity instead of picking by array order', () => { const identity = resolveCanonicalPaneAgentIdentity({ hookAgent: 'claude', diff --git a/src/shared/pane-agent-identity-resolver.test.ts b/src/shared/pane-agent-identity-resolver.test.ts index 3ffa5420f55..72ba270561d 100644 --- a/src/shared/pane-agent-identity-resolver.test.ts +++ b/src/shared/pane-agent-identity-resolver.test.ts @@ -1,5 +1,4 @@ import { describe, expect, it } from 'vitest' -import { PANE_AGENT_SOURCE_RANK } from './pane-agent-identity-adapter' import { PANE_AGENT_EVIDENCE_SOURCES, type PaneAgentEvidence, @@ -12,14 +11,6 @@ const resolve = (evidence: PaneAgentEvidence[], extra = {}) => const H = 'authority-a' describe('resolvePaneAgentIdentity', () => { - it('keeps every evidence source ranked exactly once', () => { - expect(PANE_AGENT_SOURCE_RANK).toBe(PANE_AGENT_EVIDENCE_SOURCES) - expect(new Set(PANE_AGENT_SOURCE_RANK).size).toBe(PANE_AGENT_SOURCE_RANK.length) - for (const source of PANE_AGENT_EVIDENCE_SOURCES) { - expect(PANE_AGENT_SOURCE_RANK.indexOf(source)).toBeGreaterThanOrEqual(0) - } - }) - describe('a display title is the last thing consulted', () => { it.each(PANE_AGENT_EVIDENCE_SOURCES.filter((s) => s !== 'title' && s !== 'sibling'))( 'lets %s outrank a conflicting title', @@ -39,16 +30,6 @@ describe('resolvePaneAgentIdentity', () => { source: 'title' }) }) - - it('outranks the launch record with nothing weaker than the launch record', () => { - // The tab ladder currently puts the parsed title ABOVE activeLaunchAgent, so a string Orca - // parsed beats a fact Orca owns. That inversion cannot be expressed here. - const result = resolve([ - { source: 'launch', agent: 'claude' }, - { source: 'title', agent: 'gemini' } - ]) - expect(result.agent).toBe('claude') - }) }) describe('run generation separates the bug from the legitimate reclaim', () => { @@ -78,18 +59,6 @@ describe('resolvePaneAgentIdentity', () => { expect(result).toMatchObject({ agent: 'codex', source: 'title' }) expect(result.supersededSources).toEqual(['completed-hook']) }) - - it('produces opposite answers from identical evidence, given only the run ids', () => { - const bug = resolvePaneAgentIdentity({ - evidence: evidenceFor(7, 7), - currentRun: { authorityId: H, incarnation: 7 } - }) - const reclaim = resolvePaneAgentIdentity({ - evidence: evidenceFor(7, 8), - currentRun: { authorityId: H, incarnation: 8 } - }) - expect(bug.agent).not.toBe(reclaim.agent) - }) }) describe('mixed-version peers', () => { @@ -288,9 +257,5 @@ describe('resolvePaneAgentIdentity', () => { ) expect(result).toMatchObject({ agent: 'claude', source: 'launch' }) }) - - it('leaves display consumers unrestricted when no floor is given', () => { - expect(resolve([{ source: 'title', agent: 'codex' }]).agent).toBe('codex') - }) }) }) diff --git a/src/shared/pi-state-title-marker.test.ts b/src/shared/pi-state-title-marker.test.ts index 4317f9458da..8f352bef22b 100644 --- a/src/shared/pi-state-title-marker.test.ts +++ b/src/shared/pi-state-title-marker.test.ts @@ -70,14 +70,4 @@ describe('Pi/OMP native state-title markers', () => { } ) }) - - // Every table row must reach detection without a second parser change (#13890). - it('routes every table marker through title detection', () => { - for (const marker of PI_STATE_MARKERS) { - const status = getPiStateTitleStatus(`π ${marker} my-project`) - expect(status).not.toBeNull() - expect(detectAgentStatusFromTitle(`π ${marker} my-project`)).toBe(status) - expect(detectAgentStatusFromTitle(`zsh | π ${marker} my-project`)).toBe(status) - } - }) }) diff --git a/src/shared/plugins/plugin-consent-fingerprint.test.ts b/src/shared/plugins/plugin-consent-fingerprint.test.ts index e5937432ff7..b7bc0abb0dd 100644 --- a/src/shared/plugins/plugin-consent-fingerprint.test.ts +++ b/src/shared/plugins/plugin-consent-fingerprint.test.ts @@ -1,6 +1,5 @@ -import { createHash } from 'node:crypto' import { describe, expect, it } from 'vitest' -import { canonicalizeCapabilitySet, type PluginCapability } from './plugin-capabilities' +import type { PluginCapability } from './plugin-capabilities' import { fingerprintPluginConsent } from './plugin-consent-fingerprint' import { getPluginActivationState, @@ -55,15 +54,6 @@ describe('fingerprintPluginConsent', () => { expect(needsReconsent('orca-samples.demo', withWorker, lists)).toBe(true) }) - it('preserves capability-only fingerprints for existing panel plugins', () => { - const capabilities = [workspaceRead, storage] - const legacy = `sha256-${createHash('sha256') - .update(canonicalizeCapabilitySet(capabilities)) - .digest('base64')}` - - expect(fingerprintPluginConsent({ main: undefined, capabilities })).toBe(legacy) - }) - it('requires re-consent when instructional content bytes change', () => { const subject = { main: undefined, diff --git a/src/shared/plugins/plugin-language-pack-artifact.test.ts b/src/shared/plugins/plugin-language-pack-artifact.test.ts index 68ce5aa05e9..2a79c8201b0 100644 --- a/src/shared/plugins/plugin-language-pack-artifact.test.ts +++ b/src/shared/plugins/plugin-language-pack-artifact.test.ts @@ -1,11 +1,9 @@ import { describe, expect, it } from 'vitest' import { - isPluginLanguagePackRegistration, parsePluginLanguagePackArtifact, PLUGIN_LANGUAGE_CATALOG_MAX_DEPTH, PLUGIN_LANGUAGE_CATALOG_MAX_ENTRIES, validatePluginLanguagePackCatalog, - checkPluginLanguagePackCatalog, pluginLanguageResourceId } from './plugin-language-pack-artifact' @@ -120,23 +118,6 @@ describe('plugin language-pack artifacts', () => { ).toMatchObject({ ok: false, error: expect.stringContaining('protected security copy') }) }) - it('forges no trust badge: the community→Official swap is refused', () => { - expect( - parsePluginLanguagePackArtifact( - JSON.stringify({ - auto: { - components: { - settings: { PluginConsentProvenance: { community: 'Official' } } - } - } - }) - ) - ).toMatchObject({ - ok: false, - error: expect.stringContaining('auto.components.settings.PluginConsentProvenance') - }) - }) - it('still lets language packs translate non-plugin settings copy', () => { expect( parsePluginLanguagePackArtifact( @@ -185,29 +166,4 @@ describe('plugin language-pack artifacts', () => { error: expect.stringContaining(`${PLUGIN_LANGUAGE_CATALOG_MAX_ENTRIES} entries`) }) }) - - it('shape-validates 16 maximum-entry registrations without returning catalog copies', () => { - const catalog = Object.fromEntries( - Array.from({ length: PLUGIN_LANGUAGE_CATALOG_MAX_ENTRIES }, (_, index) => [ - `key${index}`, - 'value' - ]) - ) - const packs = Array.from({ length: 16 }, (_, index) => { - const id = `plugin:maximum-${index}` as const - return { - id, - resourceLanguage: pluginLanguageResourceId(id), - pluginKey: `maximum-${index}`, - locale: 'en', - catalog - } - }) - - expect(checkPluginLanguagePackCatalog(catalog)).toEqual({ - ok: true, - entries: PLUGIN_LANGUAGE_CATALOG_MAX_ENTRIES - }) - expect(packs.filter(isPluginLanguagePackRegistration)).toHaveLength(16) - }) }) diff --git a/src/shared/plugins/plugin-panel-message-budget.test.ts b/src/shared/plugins/plugin-panel-message-budget.test.ts index aaf5d7eb77a..190a564063e 100644 --- a/src/shared/plugins/plugin-panel-message-budget.test.ts +++ b/src/shared/plugins/plugin-panel-message-budget.test.ts @@ -17,13 +17,6 @@ describe('structuredCloneMessageBytes', () => { expect(structuredCloneMessageBytes(tinyView, 256)).toBeGreaterThanOrEqual(128) expect(structuredCloneMessageBytes(new Blob([new Uint8Array(80)]), 256)).toBe(80) }) - - it('handles cycles and stops walking once the cap is exceeded', () => { - const cyclic: { self?: unknown; text: string } = { text: 'x'.repeat(100) } - cyclic.self = cyclic - - expect(structuredCloneMessageBytes(cyclic, 32)).toBe(33) - }) }) describe('createPanelMessageBudget', () => { diff --git a/src/shared/plugins/plugin-panel-shell.test.ts b/src/shared/plugins/plugin-panel-shell.test.ts index 5900d9c61f3..ad1b5d4a5d7 100644 --- a/src/shared/plugins/plugin-panel-shell.test.ts +++ b/src/shared/plugins/plugin-panel-shell.test.ts @@ -1,21 +1,9 @@ // @vitest-environment happy-dom import { describe, expect, it } from 'vitest' -import { buildPluginPanelShellHtml, PLUGIN_PANEL_CSP } from './plugin-panel-shell' +import { buildPluginPanelShellHtml } from './plugin-panel-shell' describe('buildPluginPanelShellHtml', () => { - it('places CSP and navigation guards before plugin content', () => { - const html = buildPluginPanelShellHtml('
Plugin
') - const pluginOffset = html.indexOf('plugin-content') - - expect(PLUGIN_PANEL_CSP).toContain("form-action 'none'") - expect(html.indexOf('Content-Security-Policy')).toBeLessThan(pluginOffset) - expect(html.indexOf("window.navigation.addEventListener('navigate'")).toBeLessThan(pluginOffset) - expect(html.indexOf("window.addEventListener('click'")).toBeLessThan(pluginOffset) - expect(html.indexOf("window.addEventListener('submit'")).toBeLessThan(pluginOffset) - expect(html.indexOf("Object.defineProperty(window, 'open'")).toBeLessThan(pluginOffset) - }) - it('cancels anchor and form default navigation in the fallback path', () => { const html = buildPluginPanelShellHtml('
Plugin
') const script = html.match(/