From 0fcc74ea2a40e7054549f0144ac4272a2121cae9 Mon Sep 17 00:00:00 2001 From: OrcaWin Date: Mon, 26 Oct 2026 23:03:21 -0700 Subject: [PATCH] test: remove 56 low-value cases and 5 suites (#34268) Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> --- config/scripts/ci-shard-timings.json | 1 - .../github/pr-comment-mutability.test.ts | 27 ---- src/shared/github/pr-reviewer-display.test.ts | 75 ---------- src/shared/hidden-window-frame-pump.test.ts | 23 --- .../hosted-review-create-confirmation.test.ts | 12 -- .../native-chat-interactive-prompt.test.ts | 49 ------- .../native-chat-prompt-dismiss-key.test.ts | 51 ------- src/shared/orca-hook-trust-entries.test.ts | 34 ----- src/shared/orchestration-heartbeat.test.ts | 16 --- src/shared/pr-check-step-status.test.ts | 58 -------- .../source-control-entry-actions.test.ts | 48 ------- src/shared/terminal-exit-cause.test.ts | 64 --------- src/shared/terminal-stream-protocol.test.ts | 131 ------------------ src/shared/windows-terminal-shell.test.ts | 92 ------------ 14 files changed, 681 deletions(-) delete mode 100644 src/shared/github/pr-comment-mutability.test.ts delete mode 100644 src/shared/hidden-window-frame-pump.test.ts delete mode 100644 src/shared/orca-hook-trust-entries.test.ts delete mode 100644 src/shared/pr-check-step-status.test.ts delete mode 100644 src/shared/source-control-entry-actions.test.ts diff --git a/config/scripts/ci-shard-timings.json b/config/scripts/ci-shard-timings.json index 3ad68e8a251..806aec63bec 100644 --- a/config/scripts/ci-shard-timings.json +++ b/config/scripts/ci-shard-timings.json @@ -8558,7 +8558,6 @@ "src/renderer/src/components/right-sidebar/source-control-discard-localization.test.ts": 219, "src/renderer/src/components/right-sidebar/source-control-dropdown-items.create-pr-intent.test.ts": 163, "src/renderer/src/components/right-sidebar/source-control-dropdown-items.test.ts": 256, - "src/renderer/src/components/right-sidebar/source-control-entry-actions.test.ts": 45, "src/renderer/src/components/right-sidebar/source-control-entry-context-menu.test.tsx": 367, "src/renderer/src/components/right-sidebar/source-control-file-filter.test.ts": 46, "src/renderer/src/components/right-sidebar/source-control-header-toolbar-identity.test.tsx": 346, diff --git a/src/shared/github/pr-comment-mutability.test.ts b/src/shared/github/pr-comment-mutability.test.ts deleted file mode 100644 index 17bb0cdfbdf..00000000000 --- a/src/shared/github/pr-comment-mutability.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { isMutablePRConversationComment } from './pr-comment-mutability' - -describe('isMutablePRConversationComment', () => { - it('allows top-level conversation comments but not review threads or summaries', () => { - expect( - isMutablePRConversationComment({ - id: 12, - url: 'https://github.com/acme/widgets/pull/42#issuecomment-12' - }) - ).toBe(true) - expect( - isMutablePRConversationComment({ - id: 34, - url: 'https://github.com/acme/widgets/pull/42#discussion_r34', - threadId: 'thread-1', - path: 'src/a.ts' - }) - ).toBe(false) - expect( - isMutablePRConversationComment({ - id: 99, - url: 'https://github.com/acme/widgets/pull/42#pullrequestreview-99' - }) - ).toBe(false) - }) -}) diff --git a/src/shared/github/pr-reviewer-display.test.ts b/src/shared/github/pr-reviewer-display.test.ts index 8614c9d062c..73fe33cba19 100644 --- a/src/shared/github/pr-reviewer-display.test.ts +++ b/src/shared/github/pr-reviewer-display.test.ts @@ -3,10 +3,7 @@ import { afterEach, describe, expect, it, vi } from 'vitest' import type { GitHubWorkItem } from './work-item-types' import { GITHUB_PR_REVIEWER_INPUT_MAX_BYTES, - getGitHubPRPrimaryReviewer, getGitHubPRReviewerRows, - getGitHubPRReviewLabel, - normalizeGitHubReviewerLogins, parseGitHubReviewerInputLogins } from './pr-reviewer-display' @@ -19,69 +16,6 @@ afterEach(() => { }) describe('GitHub PR reviewer display', () => { - it('shows the requested reviewer instead of a request count', () => { - expect( - getGitHubPRReviewLabel( - item({ - reviewRequests: [{ login: 'ExampleReviewer', name: null, avatarUrl: '' }] - }) - ) - ).toBe('ExampleReviewer') - }) - - it('keeps multiple reviewers compact while still naming the first reviewer', () => { - expect( - getGitHubPRReviewLabel( - item({ - reviewRequests: [ - { login: 'ExampleReviewer', name: null, avatarUrl: '' }, - { login: 'agent-slack', name: null, avatarUrl: '' }, - { login: 'stably', name: null, avatarUrl: '' } - ] - }) - ) - ).toBe('ExampleReviewer +2') - }) - - it('preserves stronger review decision labels', () => { - expect( - getGitHubPRReviewLabel( - item({ - reviewDecision: 'APPROVED', - reviewRequests: [{ login: 'ExampleReviewer', name: null, avatarUrl: '' }] - }) - ) - ).toBe('Approved') - }) - - it('falls back to reviewed users and empty metadata labels', () => { - expect(getGitHubPRReviewLabel(item({ latestReviews: [{ login: 'reviewer' }] }))).toBe( - 'reviewer' - ) - expect(getGitHubPRReviewLabel(item({ reviewRequests: [] }))).toBe('No reviewers') - expect(getGitHubPRReviewLabel(item({}))).toBe('Reviewers') - }) - - it('returns the primary reviewer avatar without requiring another lookup', () => { - expect( - getGitHubPRPrimaryReviewer( - item({ - reviewRequests: [ - { - login: 'ExampleReviewer', - name: null, - avatarUrl: 'https://avatars.githubusercontent.com/u/1?v=4' - } - ] - }) - ) - ).toEqual({ - login: 'ExampleReviewer', - name: null, - avatarUrl: 'https://avatars.githubusercontent.com/u/1?v=4' - }) - }) - it('builds reviewer rows for requested and reviewed users', () => { expect( getGitHubPRReviewerRows( @@ -109,15 +43,6 @@ describe('GitHub PR reviewer display', () => { ]) }) - it('normalizes reviewer input before sending it to GitHub', () => { - expect( - normalizeGitHubReviewerLogins( - [' @ExampleReviewer ', 'examplereviewer', '@new-reviewer'], - new Set(['existing']) - ) - ).toEqual(['ExampleReviewer', 'new-reviewer']) - }) - it('parses pasted reviewer input without regex splitting', () => { const split = vi.spyOn(String.prototype, 'split') const input = ['@ExampleReviewer,', String.fromCharCode(160), '@new-reviewer\nthird'].join('') diff --git a/src/shared/hidden-window-frame-pump.test.ts b/src/shared/hidden-window-frame-pump.test.ts deleted file mode 100644 index 17c37147749..00000000000 --- a/src/shared/hidden-window-frame-pump.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { - formatHiddenWindowFramePumpArgument, - hasHiddenWindowFramePumpArgument, - shouldPumpHiddenWindowFrames -} from './hidden-window-frame-pump' - -describe('shouldPumpHiddenWindowFrames', () => { - it('pumps only a Windows background launch', () => { - expect(shouldPumpHiddenWindowFrames({ ORCA_BACKGROUND_LAUNCH: '1' }, 'win32')).toBe(true) - expect(shouldPumpHiddenWindowFrames({ ORCA_BACKGROUND_LAUNCH: '1' }, 'darwin')).toBe(false) - expect(shouldPumpHiddenWindowFrames({ ORCA_BACKGROUND_LAUNCH: '1' }, 'linux')).toBe(false) - expect(shouldPumpHiddenWindowFrames({}, 'win32')).toBe(false) - expect(shouldPumpHiddenWindowFrames({ ORCA_BACKGROUND_LAUNCH: '0' }, 'win32')).toBe(false) - }) - - it('reads back only the argument main stamped', () => { - expect(hasHiddenWindowFramePumpArgument(['--x', formatHiddenWindowFramePumpArgument()])).toBe( - true - ) - expect(hasHiddenWindowFramePumpArgument(['--orca-browser-client-host-id=abc'])).toBe(false) - }) -}) diff --git a/src/shared/hosted-review-create-confirmation.test.ts b/src/shared/hosted-review-create-confirmation.test.ts index cc07cbe5f61..af32587b5c6 100644 --- a/src/shared/hosted-review-create-confirmation.test.ts +++ b/src/shared/hosted-review-create-confirmation.test.ts @@ -19,18 +19,6 @@ function eligibility( describe('isHostedReviewCreateEligibilityConfirmable', () => { const base = { reviewLookup: 'not_found' as const, hasHardRefreshError: false } - it('allows canCreate and needs_push when nothing hard-blocks', () => { - expect( - isHostedReviewCreateEligibilityConfirmable({ ...base, eligibility: eligibility() }) - ).toBe(true) - expect( - isHostedReviewCreateEligibilityConfirmable({ - ...base, - eligibility: eligibility({ canCreate: false, blockedReason: 'needs_push' }) - }) - ).toBe(true) - }) - it('fails closed on every hard-block: null, existing_review, positive, hard error, unavailable', () => { expect(isHostedReviewCreateEligibilityConfirmable({ ...base, eligibility: null })).toBe(false) expect( diff --git a/src/shared/native-chat-interactive-prompt.test.ts b/src/shared/native-chat-interactive-prompt.test.ts index 783ccb46c16..bd220bf249c 100644 --- a/src/shared/native-chat-interactive-prompt.test.ts +++ b/src/shared/native-chat-interactive-prompt.test.ts @@ -1,14 +1,11 @@ import { describe, expect, it } from 'vitest' import { parseApprovalFromStatus, - parseInteractivePrompt, resolveNativeChatInteractivePromptCard } from './native-chat-interactive-prompt' import type { NativeChatMessage } from './native-chat-types' import { buildOmpPlanReviewAnswer } from './omp-plan-review-prompt' -const ESC = String.fromCharCode(27) - describe('parseApprovalFromStatus', () => { it('returns null for non-approval envelopes', () => { expect(parseApprovalFromStatus(null)).toBeNull() @@ -18,30 +15,6 @@ describe('parseApprovalFromStatus', () => { expect(parseApprovalFromStatus(JSON.stringify({ approval: { tool: '' } }))).toBeNull() }) - it('builds an Allow/Deny card from { approval: { tool, summary } }', () => { - const approval = parseApprovalFromStatus( - JSON.stringify({ approval: { tool: 'Bash', summary: 'rm -rf build' } }) - ) - expect(approval).toEqual({ - title: 'Allow Bash?', - detail: 'rm -rf build', - options: [ - { label: 'Allow', send: '1' }, - { label: 'Deny', send: ESC } - ] - }) - }) - - it('omits detail for a missing, empty or non-string summary', () => { - for (const summary of [undefined, '', 42, null]) { - const approval = parseApprovalFromStatus( - JSON.stringify({ approval: { tool: 'Edit', summary } }) - ) - expect(approval?.title).toBe('Allow Edit?') - expect(approval?.detail).toBeUndefined() - } - }) - it('reads the OMP plan review envelope', () => { const planReview = JSON.stringify({ planReview: { @@ -60,28 +33,6 @@ describe('parseApprovalFromStatus', () => { }) }) -describe('parseInteractivePrompt', () => { - it('returns a question card, with question taking precedence', () => { - const card = parseInteractivePrompt( - JSON.stringify({ - questions: [{ question: 'q', options: ['A'] }], - approval: { tool: 'Bash', summary: 's' } - }) - ) - expect(card?.kind).toBe('question') - }) - - it('returns an approval card when no question is present', () => { - const card = parseInteractivePrompt(JSON.stringify({ approval: { tool: 'Bash' } })) - expect(card?.kind).toBe('approval') - }) - - it('returns null when neither parses', () => { - expect(parseInteractivePrompt(null)).toBeNull() - expect(parseInteractivePrompt('{}')).toBeNull() - }) -}) - describe('resolveNativeChatInteractivePromptCard', () => { const pendingAsk: NativeChatMessage = { id: 'm1', diff --git a/src/shared/native-chat-prompt-dismiss-key.test.ts b/src/shared/native-chat-prompt-dismiss-key.test.ts index 8888f5e78f2..1289dc8e9df 100644 --- a/src/shared/native-chat-prompt-dismiss-key.test.ts +++ b/src/shared/native-chat-prompt-dismiss-key.test.ts @@ -2,33 +2,6 @@ import { describe, expect, it } from 'vitest' import { nativeChatCardDismissKey } from './native-chat-prompt-dismiss-key' describe('nativeChatCardDismissKey', () => { - it('returns null for no card', () => { - expect(nativeChatCardDismissKey(null)).toBeNull() - }) - - it('keys a question by its full canonical content', () => { - const key = nativeChatCardDismissKey({ - kind: 'question', - prompt: { - questions: [ - { question: 'Pick a color', multiSelect: false, options: [{ label: 'Red' }] }, - { question: 'Pick a size', multiSelect: false, options: [{ label: 'L' }] } - ] - } - }) - expect(key).toContain('Pick a color') - expect(key).toContain('Pick a size') - }) - - it('gives identical questions the same key (so a lingering re-emit stays hidden)', () => { - const make = (): ReturnType => - nativeChatCardDismissKey({ - kind: 'question', - prompt: { questions: [{ question: 'Continue?', multiSelect: false, options: [] }] } - }) - expect(make()).toBe(make()) - }) - it('distinguishes prompts whose later questions or options changed', () => { const card = (second: string, option: string) => nativeChatCardDismissKey({ @@ -44,28 +17,4 @@ describe('nativeChatCardDismissKey', () => { expect(card('Old second', 'A')).not.toBe(card('New second', 'A')) expect(card('Old second', 'A')).not.toBe(card('Old second', 'B')) }) - - it('keys an approval by its title and detail', () => { - const key = nativeChatCardDismissKey({ - kind: 'approval', - approval: { - title: 'Allow Bash?', - detail: 'rm -rf build', - options: [{ label: 'Allow', send: '1' }] - } - }) - expect(key).toBe('approval:Allow Bash?:rm -rf build') - }) - - it('distinguishes different approvals', () => { - const a = nativeChatCardDismissKey({ - kind: 'approval', - approval: { title: 'Allow Bash?', options: [] } - }) - const b = nativeChatCardDismissKey({ - kind: 'approval', - approval: { title: 'Allow Write?', options: [] } - }) - expect(a).not.toBe(b) - }) }) diff --git a/src/shared/orca-hook-trust-entries.test.ts b/src/shared/orca-hook-trust-entries.test.ts deleted file mode 100644 index 1fe98d9c934..00000000000 --- a/src/shared/orca-hook-trust-entries.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { - withOrcaHookRepoAlwaysTrusted, - withOrcaHookScriptConfirmed -} from './orca-hook-trust-entries' -import type { PersistedTrustedOrcaHooks } from './orca-yaml-hook-types' - -describe('orca hook trust entries', () => { - it('preserves unrelated trust entries when confirming a script', () => { - const trust: PersistedTrustedOrcaHooks = { - 'repo-1': { archive: { contentHash: 'archive-hash', approvedAt: 1000 } } - } - - expect(withOrcaHookScriptConfirmed(trust, 'repo-1', 'setup', 'setup-hash', 2000)).toEqual({ - 'repo-1': { - archive: { contentHash: 'archive-hash', approvedAt: 1000 }, - setup: { contentHash: 'setup-hash', approvedAt: 2000 } - } - }) - }) - - it('records always-trust without dropping existing script approvals', () => { - const trust: PersistedTrustedOrcaHooks = { - 'repo-1': { setup: { contentHash: 'setup-hash', approvedAt: 1000 } } - } - - expect(withOrcaHookRepoAlwaysTrusted(trust, 'repo-1', 2000)).toEqual({ - 'repo-1': { - setup: { contentHash: 'setup-hash', approvedAt: 1000 }, - all: { approvedAt: 2000 } - } - }) - }) -}) diff --git a/src/shared/orchestration-heartbeat.test.ts b/src/shared/orchestration-heartbeat.test.ts index c4885766927..69c63eb1200 100644 --- a/src/shared/orchestration-heartbeat.test.ts +++ b/src/shared/orchestration-heartbeat.test.ts @@ -2,22 +2,6 @@ import { describe, expect, it } from 'vitest' import { resolveOrchestrationHeartbeatMinutes } from './orchestration-heartbeat' describe('resolveOrchestrationHeartbeatMinutes', () => { - it('keeps the 5-minute default for profiles saved before the settings existed', () => { - expect(resolveOrchestrationHeartbeatMinutes({})).toBe(5) - }) - - it('uses the chosen interval only while heartbeats are on', () => { - expect( - resolveOrchestrationHeartbeatMinutes({ orchestrationHeartbeatIntervalMinutes: 15 }) - ).toBe(15) - expect( - resolveOrchestrationHeartbeatMinutes({ - orchestrationHeartbeatsEnabled: false, - orchestrationHeartbeatIntervalMinutes: 15 - }) - ).toBeNull() - }) - it.each([0, 61, 2.5, Number.NaN])('falls back to 5 for a malformed interval (%s)', (value) => { expect( resolveOrchestrationHeartbeatMinutes({ orchestrationHeartbeatIntervalMinutes: value }) diff --git a/src/shared/pr-check-step-status.test.ts b/src/shared/pr-check-step-status.test.ts deleted file mode 100644 index c7f346f07a5..00000000000 --- a/src/shared/pr-check-step-status.test.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { resolveStepOutcome, summarizeJobSteps } from './pr-check-step-status' - -describe('resolveStepOutcome', () => { - it('prefers conclusion over status', () => { - expect(resolveStepOutcome({ status: 'completed', conclusion: 'failure' })).toBe('failure') - }) - - it('maps failure-like states to failure', () => { - for (const conclusion of [ - 'failure', - 'failed', - 'action_required', - 'cancelled', - 'stale', - 'startup_failure', - 'timed_out' - ]) { - expect(resolveStepOutcome({ status: null, conclusion })).toBe('failure') - } - }) - - it('maps skipped and neutral to skipped', () => { - expect(resolveStepOutcome({ status: null, conclusion: 'skipped' })).toBe('skipped') - expect(resolveStepOutcome({ status: null, conclusion: 'neutral' })).toBe('skipped') - }) - - it('treats unknown or absent states as pending', () => { - expect(resolveStepOutcome({ status: null, conclusion: null })).toBe('pending') - expect(resolveStepOutcome({ status: 'in_progress', conclusion: null })).toBe('pending') - }) -}) - -describe('summarizeJobSteps', () => { - it('buckets steps and counts the total', () => { - const step = (name: string, status: string | null, conclusion: string | null) => ({ - name, - status, - conclusion, - startedAt: null, - completedAt: null - }) - const breakdown = summarizeJobSteps({ - steps: [ - step('Typecheck', 'completed', 'failure'), - step('Lint', 'completed', 'success'), - step('Build', 'completed', 'success'), - step('Smoke test', 'completed', 'skipped'), - step('Deploy', 'queued', null) - ] - }) - expect(breakdown.failed.map((step) => step.name)).toEqual(['Typecheck']) - expect(breakdown.succeeded.map((step) => step.name)).toEqual(['Lint', 'Build']) - expect(breakdown.skipped.map((step) => step.name)).toEqual(['Smoke test']) - expect(breakdown.pending.map((step) => step.name)).toEqual(['Deploy']) - expect(breakdown.total).toBe(5) - }) -}) diff --git a/src/shared/source-control-entry-actions.test.ts b/src/shared/source-control-entry-actions.test.ts deleted file mode 100644 index e071d799a6a..00000000000 --- a/src/shared/source-control-entry-actions.test.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { describe, expect, it } from 'vitest' -import type { GitStatusEntry } from './git-status-types' -import { - canDiscardStatusEntry, - canStageStatusEntry, - canUnstageStatusEntry -} from './source-control-entry-actions' - -function entry(overrides: Partial): GitStatusEntry { - return { - path: 'file.ts', - status: 'modified', - area: 'unstaged', - ...overrides - } as GitStatusEntry -} - -describe('source control entry actions', () => { - it('hides Unstage for submodule-internal staged rows but keeps it for normal staged rows', () => { - expect(canUnstageStatusEntry(entry({ area: 'staged' }))).toBe(true) - expect(canUnstageStatusEntry(entry({ area: 'staged', submoduleRoot: 'vendor/lib' }))).toBe( - false - ) - expect(canUnstageStatusEntry(entry({ area: 'unstaged' }))).toBe(false) - }) - - it('hides Discard for submodule-internal rows and conflict rows, keeps it for normal rows', () => { - expect(canDiscardStatusEntry(entry({ area: 'unstaged' }))).toBe(true) - expect(canDiscardStatusEntry(entry({ area: 'untracked', status: 'untracked' }))).toBe(true) - expect(canDiscardStatusEntry(entry({ area: 'unstaged', submoduleRoot: 'vendor/lib' }))).toBe( - false - ) - expect(canDiscardStatusEntry(entry({ area: 'staged' }))).toBe(false) - expect(canDiscardStatusEntry(entry({ area: 'unstaged', conflictStatus: 'unresolved' }))).toBe( - false - ) - expect( - canDiscardStatusEntry(entry({ area: 'unstaged', conflictStatus: 'resolved_locally' })) - ).toBe(false) - }) - - it('hides Stage for submodule-internal rows', () => { - expect(canStageStatusEntry(entry({ area: 'unstaged' }))).toBe(true) - expect(canStageStatusEntry(entry({ area: 'unstaged', submoduleRoot: 'vendor/lib' }))).toBe( - false - ) - }) -}) diff --git a/src/shared/terminal-exit-cause.test.ts b/src/shared/terminal-exit-cause.test.ts index bef7819d617..4f26c0d2a07 100644 --- a/src/shared/terminal-exit-cause.test.ts +++ b/src/shared/terminal-exit-cause.test.ts @@ -1,7 +1,5 @@ import { describe, expect, it } from 'vitest' import { - describeTerminalExitCause, - isDeliberateTerminalExit, isProvenProcessExit, resolveProcessExitCause, resolveUnreportedExitCause @@ -31,21 +29,6 @@ describe('resolveProcessExitCause', () => { reason: 'stop_unverified' }) }) - - it('keeps a real status when the host vouches for it', () => { - expect(resolveProcessExitCause({ exitCode: 42, signal: 0 })).toEqual({ - kind: 'exited', - exitCode: 42 - }) - expect(resolveProcessExitCause({ exitCode: 0 })).toEqual({ kind: 'exited', exitCode: 0 }) - }) - - it('treats a missing signal as no signal rather than as unknown', () => { - expect(resolveProcessExitCause({ exitCode: 7, signal: null })).toEqual({ - kind: 'exited', - exitCode: 7 - }) - }) }) describe('resolveUnreportedExitCause', () => { @@ -58,61 +41,14 @@ describe('resolveUnreportedExitCause', () => { // node-pty pairs signals with 0 and the wrapper returns 0, so nonzero preserves the child’s wait status. expect(resolveUnreportedExitCause(137)).toEqual({ kind: 'exited', exitCode: 137 }) }) - - it('keeps the more specific reason for the stop sentinel', () => { - expect(resolveUnreportedExitCause(-1)).toEqual({ kind: 'unknown', reason: 'stop_unverified' }) - }) -}) - -describe('describeTerminalExitCause', () => { - it('gives every cause a sentence that does not need a number decoded', () => { - expect(describeTerminalExitCause({ kind: 'operator_close' })).toBe( - 'Terminal closed by operator request' - ) - expect(describeTerminalExitCause({ kind: 'signaled', signal: 9 })).toBe( - 'Agent process killed by signal 9' - ) - expect(describeTerminalExitCause({ kind: 'exited', exitCode: 3 })).toBe( - 'Agent process exited with code 3' - ) - expect(describeTerminalExitCause({ kind: 'unknown', reason: 'stop_unverified' })).toBe( - 'Agent process stop was requested but never confirmed' - ) - expect(describeTerminalExitCause({ kind: 'unknown', reason: 'host_status_unavailable' })).toBe( - 'Agent process ended; this host cannot report why' - ) - expect(describeTerminalExitCause({ kind: 'unknown', reason: 'cause_unreported' })).toBe( - 'Agent process ended; the reporting host did not say why' - ) - }) -}) - -describe('isDeliberateTerminalExit', () => { - it('counts only an operator close as deliberate', () => { - expect(isDeliberateTerminalExit({ kind: 'operator_close' })).toBe(true) - expect(isDeliberateTerminalExit({ kind: 'exited', exitCode: 0 })).toBe(false) - expect(isDeliberateTerminalExit({ kind: 'signaled', signal: 9 })).toBe(false) - expect(isDeliberateTerminalExit({ kind: 'unknown', reason: 'stop_unverified' })).toBe(false) - }) }) describe('isProvenProcessExit', () => { - it('accepts host-vouched process statuses', () => { - expect(isProvenProcessExit(0)).toBe(true) - expect(isProvenProcessExit(137)).toBe(true) - }) - it('rejects the synthetic loss sentinel', () => { // A host shutdown can deliver -1 for every PTY without proving process death. expect(isProvenProcessExit(-1)).toBe(false) }) - it('answers whether the process ended, not why — so it may differ from the cause on zero', () => { - // Unknown reason still permits proven death; conflating them would strand exited panes. - expect(resolveUnreportedExitCause(0)).toEqual({ kind: 'unknown', reason: 'cause_unreported' }) - expect(isProvenProcessExit(0)).toBe(true) - }) - it('keeps a shell that a user closed with `exit` tearing down on macOS', () => { // macOS login hides the cause but waits for the shell; its exit still proves shell death and permits teardown. expect( diff --git a/src/shared/terminal-stream-protocol.test.ts b/src/shared/terminal-stream-protocol.test.ts index f8784458aac..1b9b733cb65 100644 --- a/src/shared/terminal-stream-protocol.test.ts +++ b/src/shared/terminal-stream-protocol.test.ts @@ -3,142 +3,11 @@ import { TerminalStreamOpcode, decodeTerminalStreamFrame, decodeTerminalStreamJson, - decodeTerminalStreamText, encodeTerminalStreamFrame, - encodeTerminalStreamJson, - encodeTerminalStreamText, TERMINAL_STREAM_JSON_STRUCTURE_LIMITS } from './terminal-stream-protocol' describe('terminal-stream-protocol', () => { - it('round-trips fixed-width binary frame headers and payloads', () => { - const payload = encodeTerminalStreamText('hello terminal') - const encoded = encodeTerminalStreamFrame({ - opcode: TerminalStreamOpcode.Output, - streamId: 42, - seq: 9, - payload - }) - - const decoded = decodeTerminalStreamFrame(encoded) - - expect(decoded?.opcode).toBe(TerminalStreamOpcode.Output) - expect(decoded?.streamId).toBe(42) - expect(decoded?.seq).toBe(9) - expect(decoded ? decodeTerminalStreamText(decoded.payload) : '').toBe('hello terminal') - }) - - it('round-trips snapshot metadata JSON payloads', () => { - const encoded = encodeTerminalStreamFrame({ - opcode: TerminalStreamOpcode.SnapshotStart, - streamId: 7, - seq: 1, - payload: encodeTerminalStreamJson({ kind: 'scrollback', cols: 49, rows: 28 }) - }) - - const decoded = decodeTerminalStreamFrame(encoded) - - expect(decoded && decodeTerminalStreamJson(decoded.payload)).toEqual({ - kind: 'scrollback', - cols: 49, - rows: 28 - }) - }) - - it('round-trips terminal input and resize frames', () => { - const input = decodeTerminalStreamFrame( - encodeTerminalStreamFrame({ - opcode: TerminalStreamOpcode.Input, - streamId: 11, - seq: 1, - payload: encodeTerminalStreamText('a') - }) - ) - const resize = decodeTerminalStreamFrame( - encodeTerminalStreamFrame({ - opcode: TerminalStreamOpcode.Resize, - streamId: 11, - seq: 2, - payload: encodeTerminalStreamJson({ cols: 120, rows: 40 }) - }) - ) - - expect(input?.opcode).toBe(TerminalStreamOpcode.Input) - expect(input ? decodeTerminalStreamText(input.payload) : '').toBe('a') - expect(resize?.opcode).toBe(TerminalStreamOpcode.Resize) - expect(resize && decodeTerminalStreamJson(resize.payload)).toEqual({ cols: 120, rows: 40 }) - }) - - it('round-trips terminal metadata frames', () => { - const metadata = decodeTerminalStreamFrame( - encodeTerminalStreamFrame({ - opcode: TerminalStreamOpcode.Metadata, - streamId: 11, - seq: 4, - payload: encodeTerminalStreamJson({ cwd: '/repo/src' }) - }) - ) - - expect(metadata?.opcode).toBe(TerminalStreamOpcode.Metadata) - expect(metadata && decodeTerminalStreamJson(metadata.payload)).toEqual({ cwd: '/repo/src' }) - }) - - it('round-trips multiplex subscribe, snapshot request, and unsubscribe frames', () => { - const subscribe = decodeTerminalStreamFrame( - encodeTerminalStreamFrame({ - opcode: TerminalStreamOpcode.Subscribe, - streamId: 0, - seq: 1, - payload: encodeTerminalStreamJson({ - streamId: 12, - terminal: 'terminal-1', - viewport: { cols: 120, rows: 40 } - }) - }) - ) - const unsubscribe = decodeTerminalStreamFrame( - encodeTerminalStreamFrame({ - opcode: TerminalStreamOpcode.Unsubscribe, - streamId: 12, - seq: 2, - payload: new Uint8Array() - }) - ) - const snapshotRequest = decodeTerminalStreamFrame( - encodeTerminalStreamFrame({ - opcode: TerminalStreamOpcode.SnapshotRequest, - streamId: 12, - seq: 3, - payload: new Uint8Array() - }) - ) - - expect(subscribe?.opcode).toBe(TerminalStreamOpcode.Subscribe) - expect(subscribe && decodeTerminalStreamJson(subscribe.payload)).toMatchObject({ - streamId: 12, - terminal: 'terminal-1' - }) - expect(snapshotRequest?.opcode).toBe(TerminalStreamOpcode.SnapshotRequest) - expect(snapshotRequest?.streamId).toBe(12) - expect(unsubscribe?.opcode).toBe(TerminalStreamOpcode.Unsubscribe) - expect(unsubscribe?.streamId).toBe(12) - }) - - it('round-trips output acknowledgement frames', () => { - const ack = decodeTerminalStreamFrame( - encodeTerminalStreamFrame({ - opcode: TerminalStreamOpcode.Ack, - streamId: 12, - seq: 4, - payload: encodeTerminalStreamJson({ bytes: 4096 }) - }) - ) - - expect(ack?.opcode).toBe(TerminalStreamOpcode.Ack) - expect(ack?.streamId).toBe(12) - expect(ack && decodeTerminalStreamJson(ack.payload)).toEqual({ bytes: 4096 }) - }) - it('rejects excessive JSON nesting before JSON.parse', () => { const parseSpy = vi.spyOn(JSON, 'parse') try { diff --git a/src/shared/windows-terminal-shell.test.ts b/src/shared/windows-terminal-shell.test.ts index 691c236ad50..d68a8af9c1a 100644 --- a/src/shared/windows-terminal-shell.test.ts +++ b/src/shared/windows-terminal-shell.test.ts @@ -2,69 +2,10 @@ import { describe, expect, it } from 'vitest' import { canonicalizeWindowsShellOverride, isSupportedWindowsShellOverride, - listSupportedWindowsShellOverrides, resolveWindowsShellStartupFamily } from './windows-terminal-shell' -describe('resolveWindowsShellStartupFamily', () => { - it('defaults to PowerShell when unset', () => { - expect(resolveWindowsShellStartupFamily(undefined)).toBe('powershell') - expect(resolveWindowsShellStartupFamily(null)).toBe('powershell') - expect(resolveWindowsShellStartupFamily(' ')).toBe('powershell') - }) - - it('treats PowerShell and pwsh as PowerShell', () => { - expect(resolveWindowsShellStartupFamily('powershell.exe')).toBe('powershell') - expect(resolveWindowsShellStartupFamily('pwsh.exe')).toBe('powershell') - expect(resolveWindowsShellStartupFamily('C:\\Program Files\\PowerShell\\7\\pwsh.exe')).toBe( - 'powershell' - ) - }) - - it('maps cmd.exe to cmd quoting', () => { - expect(resolveWindowsShellStartupFamily('cmd.exe')).toBe('cmd') - expect(resolveWindowsShellStartupFamily('C:\\Windows\\System32\\cmd.exe')).toBe('cmd') - }) - - it('maps Git Bash and WSL shells to POSIX quoting', () => { - expect(resolveWindowsShellStartupFamily('git-bash')).toBe('posix') - expect(resolveWindowsShellStartupFamily('wsl.exe')).toBe('posix') - expect(resolveWindowsShellStartupFamily('C:\\Program Files\\Git\\bin\\bash.exe')).toBe('posix') - }) - - it('maps extension-less bash and wsl entries to POSIX quoting', () => { - expect(resolveWindowsShellStartupFamily('bash')).toBe('posix') - expect(resolveWindowsShellStartupFamily('wsl')).toBe('posix') - expect(resolveWindowsShellStartupFamily('C:\\Program Files\\Git\\bin\\bash')).toBe('posix') - }) -}) - describe('isSupportedWindowsShellOverride', () => { - // Spelled out rather than looped over the list, which would assert the list against itself. - it('accepts exactly the shells the relay is willing to spawn', () => { - expect(listSupportedWindowsShellOverrides()).toEqual([ - 'bash', - 'bash.exe', - 'cmd', - 'cmd.exe', - 'git-bash', - 'powershell', - 'powershell.exe', - 'pwsh', - 'pwsh.exe', - 'wsl', - 'wsl.exe' - ]) - expect(isSupportedWindowsShellOverride('cmd.exe')).toBe(true) - expect(isSupportedWindowsShellOverride('powershell.exe')).toBe(true) - expect(isSupportedWindowsShellOverride('git-bash')).toBe(true) - }) - - it('accepts a differently cased spelling of an allowed shell', () => { - expect(isSupportedWindowsShellOverride('CMD.EXE')).toBe(true) - expect(isSupportedWindowsShellOverride('PowerShell.exe')).toBe(true) - }) - // The allowlist is what stops `--shell` from naming an arbitrary executable to spawn. it('refuses anything else, including a path to an allowed shell', () => { expect(isSupportedWindowsShellOverride('nu.exe')).toBe(false) @@ -75,39 +16,6 @@ describe('isSupportedWindowsShellOverride', () => { }) describe('canonicalizeWindowsShellOverride', () => { - it('maps every accepted spelling to the `.exe` name the spawn path exact-matches', () => { - expect(canonicalizeWindowsShellOverride('cmd')).toBe('cmd.exe') - expect(canonicalizeWindowsShellOverride('cmd.exe')).toBe('cmd.exe') - expect(canonicalizeWindowsShellOverride('powershell')).toBe('powershell.exe') - expect(canonicalizeWindowsShellOverride('powershell.exe')).toBe('powershell.exe') - expect(canonicalizeWindowsShellOverride('pwsh')).toBe('pwsh.exe') - expect(canonicalizeWindowsShellOverride('pwsh.exe')).toBe('pwsh.exe') - expect(canonicalizeWindowsShellOverride('wsl')).toBe('wsl.exe') - expect(canonicalizeWindowsShellOverride('wsl.exe')).toBe('wsl.exe') - expect(canonicalizeWindowsShellOverride('bash')).toBe('bash.exe') - expect(canonicalizeWindowsShellOverride('bash.exe')).toBe('bash.exe') - expect(canonicalizeWindowsShellOverride('git-bash')).toBe('git-bash') - }) - - // pwsh (PowerShell 7) and powershell (Windows PowerShell 5.1) are different binaries. - it('never collapses pwsh into powershell', () => { - expect(canonicalizeWindowsShellOverride('pwsh')).not.toBe('powershell.exe') - expect(canonicalizeWindowsShellOverride('pwsh.exe')).not.toBe('powershell.exe') - }) - - // `resolveWindowsGitBashShellPath` compares the marker case-sensitively. - it('folds case so a mixed-case spelling reaches the exact-match consumers', () => { - expect(canonicalizeWindowsShellOverride('Git-Bash')).toBe('git-bash') - expect(canonicalizeWindowsShellOverride('CMD')).toBe('cmd.exe') - expect(canonicalizeWindowsShellOverride('PowerShell.exe')).toBe('powershell.exe') - }) - - it('returns undefined for anything the allowlist refuses', () => { - expect(canonicalizeWindowsShellOverride('nu.exe')).toBeUndefined() - expect(canonicalizeWindowsShellOverride('C:\\Windows\\System32\\cmd.exe')).toBeUndefined() - expect(canonicalizeWindowsShellOverride('')).toBeUndefined() - }) - // Bare cmd falls through to PowerShell quoting; canonical cmd.exe must align spawn and queued-command grammar. it('yields the cmd startup family for a bare cmd override', () => { expect(resolveWindowsShellStartupFamily('cmd')).toBe('powershell')