diff --git a/src/main/browser/cdp-print-to-pdf.test.ts b/src/main/browser/cdp-print-to-pdf.test.ts index 4ecf8c50b09..abcf76ccf21 100644 --- a/src/main/browser/cdp-print-to-pdf.test.ts +++ b/src/main/browser/cdp-print-to-pdf.test.ts @@ -5,46 +5,6 @@ const DEFAULT_MARGIN_INCHES = 1 / 2.54 const TTL_MS = 5 * 60 * 1000 describe('buildPrintToPdfOptions', () => { - it('returns empty options for empty params', () => { - expect(buildPrintToPdfOptions({})).toEqual({}) - }) - - it('maps every well-formed field', () => { - expect( - buildPrintToPdfOptions({ - landscape: true, - displayHeaderFooter: true, - printBackground: true, - preferCSSPageSize: true, - generateTaggedPDF: true, - generateDocumentOutline: false, - scale: 1.5, - paperWidth: 8.5, - paperHeight: 11, - marginTop: 0.25, - marginBottom: 0.5, - marginLeft: 0.75, - marginRight: 1, - pageRanges: '1-2', - headerTemplate: '', - footerTemplate: '' - }) - ).toEqual({ - landscape: true, - displayHeaderFooter: true, - printBackground: true, - preferCSSPageSize: true, - generateTaggedPDF: true, - generateDocumentOutline: false, - scale: 1.5, - pageSize: { width: 8.5, height: 11 }, - margins: { top: 0.25, bottom: 0.5, left: 0.75, right: 1 }, - pageRanges: '1-2', - headerTemplate: '', - footerTemplate: '' - }) - }) - it('defaults omitted margin sides to 1cm when any side is set', () => { expect(buildPrintToPdfOptions({ marginTop: 0.25 }).margins).toEqual({ top: 0.25, @@ -53,26 +13,6 @@ describe('buildPrintToPdfOptions', () => { right: DEFAULT_MARGIN_INCHES }) }) - - it('drops non-finite and out-of-range numeric options', () => { - const options = buildPrintToPdfOptions({ - scale: 0, - paperWidth: Number.POSITIVE_INFINITY, - paperHeight: 11, - marginTop: Number.NaN - }) - expect(options.scale).toBeUndefined() - expect(options.pageSize).toBeUndefined() - expect(options.margins).toBeUndefined() - }) - - it('requires both paper dimensions for pageSize', () => { - expect(buildPrintToPdfOptions({ paperWidth: 8.5 }).pageSize).toBeUndefined() - }) - - it('ignores wrong-typed options', () => { - expect(buildPrintToPdfOptions({ landscape: 'yes', pageRanges: 7 })).toEqual({}) - }) }) describe('CdpPdfStreamStore', () => { @@ -91,11 +31,6 @@ describe('CdpPdfStreamStore', () => { expect(store.ownsHandle({})).toBe(false) }) - it('mints distinct handles per stream', () => { - const store = new CdpPdfStreamStore() - expect(store.create(Buffer.from('a'))).not.toBe(store.create(Buffer.from('b'))) - }) - it('reads sequential chunks and reports eof', () => { const store = new CdpPdfStreamStore() const handle = store.create(Buffer.from('abcdef')) @@ -127,28 +62,6 @@ describe('CdpPdfStreamStore', () => { expect(store.read({ handle, offset: 99 })).toEqual({ data: '', eof: true }) }) - it('returns null for unknown handles', () => { - const store = new CdpPdfStreamStore() - expect(store.read({ handle: 'nope' })).toBeNull() - expect(store.read({})).toBeNull() - }) - - it('drops a stream on close', () => { - const store = new CdpPdfStreamStore() - const handle = store.create(Buffer.from('abc')) - - store.close({ handle }) - expect(store.read({ handle })).toBeNull() - }) - - it('drops all streams on clear', () => { - const store = new CdpPdfStreamStore() - const handle = store.create(Buffer.from('abc')) - - store.clear() - expect(store.read({ handle })).toBeNull() - }) - it('evicts an abandoned stream after the TTL', () => { vi.useFakeTimers() const store = new CdpPdfStreamStore() diff --git a/src/main/github/pr-refresh-error-classification.test.ts b/src/main/github/pr-refresh-error-classification.test.ts index ef55c96577d..cdda943b600 100644 --- a/src/main/github/pr-refresh-error-classification.test.ts +++ b/src/main/github/pr-refresh-error-classification.test.ts @@ -1,72 +1,13 @@ import { describe, expect, it } from 'vitest' -import { - classifyPRRefreshError, - safePRRefreshErrorMessage -} from './pr-refresh-error-classification' +import { classifyPRRefreshError } from './pr-refresh-error-classification' describe('classifyPRRefreshError', () => { - it('classifies an HTTP 429 as rate_limited even without a rate-limit body', () => { - expect(classifyPRRefreshError(new Error('HTTP 429 Too Many Requests'))).toBe('rate_limited') - }) - - it('classifies secondary rate limit markers as rate_limited, not permission', () => { - for (const message of [ - 'You have exceeded a secondary rate limit', - 'abuse detection mechanism triggered', - 'abuse-rate-limits', - 'you have triggered an abuse detection mechanism' - ]) { - expect(classifyPRRefreshError(new Error(message))).toBe('rate_limited') - } - }) - it('classifies a 403 carrying Retry-After as rate_limited, not permission', () => { expect(classifyPRRefreshError(new Error('HTTP 403 Forbidden; Retry-After: 60'))).toBe( 'rate_limited' ) }) - it('classifies the primary breaker language as rate_limited', () => { - expect(classifyPRRefreshError(new Error('API rate limit exceeded for user'))).toBe( - 'rate_limited' - ) - }) - - it('classifies a plain 403 resource denial as permission', () => { - expect( - classifyPRRefreshError(new Error('HTTP 403: Resource not accessible by integration')) - ).toBe('permission') - }) - - it('classifies network failures', () => { - for (const message of ['ETIMEDOUT', 'could not resolve host github.com', 'network is down']) { - expect(classifyPRRefreshError(new Error(message))).toBe('network') - } - }) - - it('classifies 404 / could not resolve repository as repo_unavailable', () => { - expect(classifyPRRefreshError(new Error('HTTP 404 Not Found'))).toBe('repo_unavailable') - expect( - classifyPRRefreshError(new Error('Could not resolve to a Repository with the name')) - ).toBe('repo_unavailable') - }) - - it('classifies an ENOENT spawn failure as gh_unavailable', () => { - const err = Object.assign(new Error('spawn gh ENOENT'), { code: 'ENOENT' }) - expect(classifyPRRefreshError(err)).toBe('gh_unavailable') - expect(classifyPRRefreshError(new Error("'gh' is not recognized as an internal command"))).toBe( - 'gh_unavailable' - ) - }) - - it('classifies auth failures after rate-limit and permission checks', () => { - expect(classifyPRRefreshError(new Error('authentication failed: bad credentials'))).toBe('auth') - }) - - it('falls back to unknown', () => { - expect(classifyPRRefreshError(new Error('something unexpected happened'))).toBe('unknown') - }) - it('does not classify a message that merely contains "author" as auth', () => { for (const message of [ 'PR author octocat has no write access to the fork', @@ -91,10 +32,6 @@ describe('classifyPRRefreshError', () => { ).toBe('repo_unavailable') }) - it('classifies a 401 as auth', () => { - expect(classifyPRRefreshError(new Error('HTTP 401 Unauthorized'))).toBe('auth') - }) - it('classifies from the real runner error shape where the signal is on .stderr', () => { // The runner stores diagnostics on stderr even when message is only a generic exit error. const permission = Object.assign(new Error('gh exited with 1.'), { @@ -119,21 +56,3 @@ describe('classifyPRRefreshError', () => { expect(classifyPRRefreshError(auth)).toBe('auth') }) }) - -describe('safePRRefreshErrorMessage', () => { - it('returns non-empty copy for every classified type without leaking raw errors', () => { - for (const type of [ - 'rate_limited', - 'auth', - 'network', - 'permission', - 'repo_unavailable', - 'gh_unavailable', - 'unknown' - ] as const) { - const message = safePRRefreshErrorMessage(type) - expect(message.length).toBeGreaterThan(0) - expect(message).not.toContain('ENOENT') - } - }) -}) diff --git a/src/main/ipc/notification-options-localization.test.ts b/src/main/ipc/notification-options-localization.test.ts index 891542d368b..2b0b7189446 100644 --- a/src/main/ipc/notification-options-localization.test.ts +++ b/src/main/ipc/notification-options-localization.test.ts @@ -43,12 +43,4 @@ describe('buildNotificationOptions localization', () => { body: 'Un agente de programación terminó de trabajar.' }) }) - - it('keeps the English copy for headless and initialized English translators', () => { - for (const translate of [undefined, translateMain]) { - expect(buildNotificationOptions(BELL, translate).title).toBe('Bell in feature-x') - expect(buildNotificationOptions(TOOL, translate).body).toBe('Using Bash: ls {{tool}}') - expect(buildNotificationOptions(FALLBACK, translate).title).toBe('Task complete in workspace') - } - }) }) diff --git a/src/main/ipc/notifications-message-formatting.test.ts b/src/main/ipc/notifications-message-formatting.test.ts index 42ff0acacea..5d619cc144f 100644 --- a/src/main/ipc/notifications-message-formatting.test.ts +++ b/src/main/ipc/notifications-message-formatting.test.ts @@ -3,7 +3,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { getDispatchHandler, notificationCtorMock, - notificationShowMock, resetNotificationDispatchMocks } from './notifications-test-harness' @@ -32,106 +31,6 @@ describe('registerNotificationHandlers', () => { resetNotificationDispatchMocks() }) - it('delivers a notification when the event is allowed', async () => { - registerNotificationHandlers({ - getSettings: () => ({ - notifications: { - enabled: true, - agentTaskComplete: true, - terminalBell: false, - suppressWhenFocused: true - } - }) - } as never) - - const handler = getDispatchHandler() - expect( - await handler( - {}, - { source: 'agent-task-complete', repoLabel: 'orca', worktreeLabel: 'feat/notis' } - ) - ).toEqual({ delivered: true }) - expect(notificationCtorMock).toHaveBeenCalledWith({ - title: 'Task complete in feat/notis', - body: 'orca' - }) - expect(notificationShowMock).toHaveBeenCalledTimes(1) - }) - - it('formats agent-task-complete with the agent response when a status snapshot is present', async () => { - registerNotificationHandlers({ - getSettings: () => ({ - notifications: { - enabled: true, - agentTaskComplete: true, - terminalBell: false, - suppressWhenFocused: true - } - }) - } as never) - - const handler = getDispatchHandler() - expect( - await handler( - {}, - { - source: 'agent-task-complete', - worktreeId: 'repo::wt1', - worktreeLabel: 'feat/notis', - repoLabel: 'orca', - terminalTitle: '* Claude done', - agentType: 'codex', - agentState: 'done', - agentPrompt: 'Fix rich notification text', - agentLastAssistantMessage: 'Updated the notification body.' - } - ) - ).toEqual({ delivered: true }) - - expect(notificationCtorMock).toHaveBeenCalledWith({ - title: 'orca / feat/notis - Codex finished', - body: 'Updated the notification body.' - }) - }) - - it.each([true, false, undefined])( - 'includes the repo name regardless of the legacy multiple-repo flag (%s)', - async (hasMultipleActiveRepos) => { - registerNotificationHandlers({ - getSettings: () => ({ - notifications: { - enabled: true, - agentTaskComplete: true, - terminalBell: false, - suppressWhenFocused: true - } - }) - } as never) - - const handler = getDispatchHandler() - expect( - await handler( - {}, - { - source: 'agent-task-complete', - worktreeId: 'repo::wt1', - worktreeLabel: 'feat/notis', - repoLabel: 'orca', - hasMultipleActiveRepos, - agentType: 'codex', - agentState: 'done', - agentLastAssistantMessage: 'Updated the notification body.' - } - ) - ).toEqual({ delivered: true }) - - expect(notificationCtorMock).toHaveBeenCalledWith({ - title: 'orca / feat/notis - Codex finished', - body: 'Updated the notification body.' - }) - } - ) - it('keeps a readable body when no assistant response was captured', async () => { registerNotificationHandlers({ getSettings: () => ({ @@ -167,38 +66,6 @@ describe('registerNotificationHandlers', () => { }) }) - it.each([ - { agentType: 'opencode2', label: 'OpenCode 2' }, - { agentType: 'copilot', label: 'GitHub Copilot' } - ])('names $agentType the way the app does', async ({ agentType, label }) => { - registerNotificationHandlers({ - getSettings: () => ({ - notifications: { - enabled: true, - agentTaskComplete: true, - terminalBell: false, - suppressWhenFocused: true - } - }) - } as never) - - await getDispatchHandler()( - {}, - { - source: 'agent-task-complete', - worktreeId: 'repo::wt1', - worktreeLabel: 'feat/notis', - agentType, - agentState: 'done' - } - ) - - expect(notificationCtorMock).toHaveBeenCalledWith({ - title: `feat/notis - ${label} finished`, - body: `${label} finished.` - }) - }) - it('formats blocked and interrupted agent snapshots distinctly', async () => { registerNotificationHandlers({ getSettings: () => ({ @@ -292,137 +159,4 @@ describe('registerNotificationHandlers', () => { expect(options.body).not.toContain('\n') expect(options.body.length).toBeLessThanOrEqual(180) }) - - it.each([ - { agentState: 'working', expected: 'feat/notis - Claude working' }, - { agentState: 'blocked', expected: 'feat/notis - Claude needs input' }, - { agentState: 'waiting', expected: 'feat/notis - Claude needs input' }, - { agentState: 'done', expected: 'feat/notis - Claude finished' }, - { agentState: undefined, expected: 'feat/notis - Claude finished' } - ])('titles agentState $agentState without claiming a false finish', async (scenario) => { - registerNotificationHandlers({ - getSettings: () => ({ - notifications: { - enabled: true, - agentTaskComplete: true, - terminalBell: false, - suppressWhenFocused: true - } - }) - } as never) - - const handler = getDispatchHandler() - await handler( - {}, - { - source: 'agent-task-complete', - worktreeLabel: 'feat/notis', - agentType: 'claude', - ...(scenario.agentState ? { agentState: scenario.agentState } : {}), - agentLastAssistantMessage: 'Ran the suite.' - } - ) - - expect(notificationCtorMock).toHaveBeenCalledWith({ - title: scenario.expected, - body: 'Ran the suite.' - }) - }) - - it.each([ - { agentTurnOutcome: 'cancellation', word: 'stopped' }, - { agentTurnOutcome: 'failure', word: 'failed' }, - // A turn cut short by anything but the user is a fault, worded as one. - { agentTurnOutcome: 'interruption', word: 'failed' }, - { agentTurnOutcome: 'unconfirmed', word: 'stopped' }, - { agentTurnOutcome: 'superseded', word: 'stopped' }, - { agentTurnOutcome: 'success', word: 'finished' }, - { agentTurnOutcome: undefined, word: 'finished' } - ] as const)('words a $agentTurnOutcome finish as $word', async ({ agentTurnOutcome, word }) => { - registerNotificationHandlers({ - getSettings: () => ({ - notifications: { - enabled: true, - agentTaskComplete: true, - terminalBell: false, - suppressWhenFocused: true - } - }) - } as never) - - const handler = getDispatchHandler() - await handler( - {}, - { - source: 'agent-task-complete', - worktreeLabel: 'feat/notis', - agentType: 'claude', - agentState: 'done', - ...(agentTurnOutcome ? { agentTurnOutcome } : {}) - } - ) - - expect(notificationCtorMock).toHaveBeenCalledWith({ - title: `feat/notis - Claude ${word}`, - body: `Claude ${word}.` - }) - }) - - it('counts a success verdict alone as an agent snapshot', async () => { - registerNotificationHandlers({ - getSettings: () => ({ - notifications: { - enabled: true, - agentTaskComplete: true, - terminalBell: false, - suppressWhenFocused: true - } - }) - } as never) - - const handler = getDispatchHandler() - await handler( - {}, - { source: 'agent-task-complete', worktreeLabel: 'feat/notis', agentTurnOutcome: 'success' } - ) - - expect(notificationCtorMock).toHaveBeenCalledWith({ - title: 'feat/notis - Agent finished', - body: 'Agent finished.' - }) - }) - - it('uses tool context before falling back when no prompt or assistant preview exists', async () => { - registerNotificationHandlers({ - getSettings: () => ({ - notifications: { - enabled: true, - agentTaskComplete: true, - terminalBell: false, - suppressWhenFocused: true - } - }) - } as never) - - const handler = getDispatchHandler() - expect( - await handler( - {}, - { - source: 'agent-task-complete', - worktreeId: 'repo::wt1', - worktreeLabel: 'feat/notis', - agentType: 'unknown', - agentState: 'working', - agentToolName: 'Bash', - agentToolInput: 'pnpm test' - } - ) - ).toEqual({ delivered: true }) - - expect(notificationCtorMock).toHaveBeenCalledWith({ - title: 'feat/notis - Agent working', - body: 'Using Bash: pnpm test' - }) - }) })