diff --git a/config/scripts/ssh-windows-mobile-bundle.test.mjs b/config/scripts/ssh-windows-mobile-bundle.test.mjs index 5a8a15b2c20..9e1d6e2a09c 100644 --- a/config/scripts/ssh-windows-mobile-bundle.test.mjs +++ b/config/scripts/ssh-windows-mobile-bundle.test.mjs @@ -94,8 +94,11 @@ it.skipIf(process.platform === 'win32')( cwd: directory }) expect(execute().code).toBe(0) - writeFileSync(join(bundleDir, 'index.html'), 'corrupt') - expect(execute().code).not.toBe(0) + const indexPath = join(bundleDir, 'index.html') + writeFileSync(indexPath, 'x'.repeat(readFileSync(indexPath).length)) + const corrupted = execute() + expect(corrupted.code).not.toBe(0) + expect(corrupted.stderr).toContain('hashes to') } finally { rmSync(directory, { recursive: true, force: true }) } diff --git a/config/scripts/undeclared-dependency-resolution.test.ts b/config/scripts/undeclared-dependency-resolution.test.ts index 5df467c56a3..399834dd6cb 100644 --- a/config/scripts/undeclared-dependency-resolution.test.ts +++ b/config/scripts/undeclared-dependency-resolution.test.ts @@ -1,6 +1,7 @@ import { existsSync, readFileSync, realpathSync } from 'node:fs' import { createRequire } from 'node:module' import { join } from 'node:path' +import { pathToFileURL } from 'node:url' import { describe, expect, it } from 'vitest' import { parse } from 'yaml' import { z } from 'zod' @@ -67,7 +68,13 @@ describe('undeclared dependency resolution', () => { } ) - it('aliases the DOM-free decode-named-character-reference entry', () => { - expect(markdownParserAliases['decode-named-character-reference']).toMatch(/[\\/]index\.js$/) + it('decodes named references through the DOM-free parser alias', async () => { + const decoder = await import( + pathToFileURL(markdownParserAliases['decode-named-character-reference']).href + ) + expect(typeof decoder.decodeNamedCharacterReference).toBe('function') + expect(decoder.decodeNamedCharacterReference('amp')).toBe('&') + expect(decoder.decodeNamedCharacterReference('NotEqualTilde')).toBe('≂̸') + expect(decoder.decodeNamedCharacterReference('not-an-entity')).toBe(false) }) }) diff --git a/mobile/src/session/mobile-clipboard-image.test.ts b/mobile/src/session/mobile-clipboard-image.test.ts index f60e3943750..c798e4c0823 100644 --- a/mobile/src/session/mobile-clipboard-image.test.ts +++ b/mobile/src/session/mobile-clipboard-image.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it, vi } from 'vitest' +import { AppendImageUploadChunk } from '../../../src/shared/rpc-contract/clipboard-params' import { buildMobileImagePastePayload, buildMobileTerminalImagePastePayload, @@ -63,6 +64,12 @@ describe('mobile clipboard image paste helpers', () => { }) ).resolves.toBe('/tmp/orca-paste-image.png') + for (const call of client.calls) { + if (call.method === 'clipboard.appendImageUploadChunk') { + expect(AppendImageUploadChunk.safeParse(call.params).success).toBe(true) + } + } + expect(client.calls).toEqual([ { method: 'clipboard.startImageUpload', diff --git a/mobile/src/session/mobile-image-attachment.test.ts b/mobile/src/session/mobile-image-attachment.test.ts index 6c8e6096bb5..29e81f80ee3 100644 --- a/mobile/src/session/mobile-image-attachment.test.ts +++ b/mobile/src/session/mobile-image-attachment.test.ts @@ -30,7 +30,7 @@ describe('attachMobileImageToTerminal', () => { async (agent) => { const client = clientWithResponses([ ok('start', { uploadId: 'upload-1' }), - ok('append', { received: 4 }), + ok('append', { receivedBase64Length: 4 }), ok('commit', '/tmp/codex-shot.png'), ok('send', { send: { accepted: true } }) ]) @@ -128,7 +128,7 @@ describe('attachMobileImageToTerminal', () => { }) }) - it('passes the active worktree connectionId to the upload', async () => { + it('uploads the selected image bytes with the active worktree connectionId', async () => { const client = clientWithResponses([ { id: 'start', @@ -150,7 +150,7 @@ describe('attachMobileImageToTerminal', () => { }) const saveCall = client.calls.find((c) => c.method === 'clipboard.saveImageAsTempFile') - expect(saveCall?.params).toMatchObject({ connectionId: 'conn-ssh' }) + expect(saveCall?.params).toEqual({ contentBase64: 'BBBB', connectionId: 'conn-ssh' }) }) it('does nothing and returns false when the picker is cancelled', async () => { diff --git a/mobile/src/session/mobile-image-source-picker.test.ts b/mobile/src/session/mobile-image-source-picker.test.ts index 35e3cff3b60..851c9e84e13 100644 --- a/mobile/src/session/mobile-image-source-picker.test.ts +++ b/mobile/src/session/mobile-image-source-picker.test.ts @@ -120,10 +120,10 @@ describe('pickMobileImage', () => { }) ) - expect(result.map((image) => image.uri)).toEqual([ - 'file:///a.jpg', - 'file:///b.jpg', - 'file:///c.jpg' + expect(result).toEqual([ + { uri: 'file:///a.jpg', base64: 'AQ==' }, + { uri: 'file:///b.jpg', base64: 'Ag==' }, + { uri: 'file:///c.jpg', base64: 'Aw==' } ]) expect(launchLibrary).toHaveBeenCalledWith( expect.objectContaining({ diff --git a/src/renderer/src/runtime/runtime-file-range-client.test.ts b/src/renderer/src/runtime/runtime-file-range-client.test.ts index de38ea733b3..d235ed795a3 100644 --- a/src/renderer/src/runtime/runtime-file-range-client.test.ts +++ b/src/renderer/src/runtime/runtime-file-range-client.test.ts @@ -132,13 +132,13 @@ describe('CSV range routing', () => { await expect(readRuntimeFileRange(localArgs, -1, 3)).rejects.toThrow('position') await expect(readRuntimeFileRange(localArgs, 0, 256 * 1024 + 1)).rejects.toThrow('limit') expect(fsReadFileChunk).not.toHaveBeenCalled() - for (const result of [ - { contentBase64: '', bytesRead: 0 }, - { contentBase64: 'YWJj', bytesRead: 2 }, - { contentBase64: 'YWJj', bytesRead: 3 } + for (const { result, length } of [ + { result: { contentBase64: '', bytesRead: 0 }, length: 2 }, + { result: { contentBase64: 'YWJj', bytesRead: 2 }, length: 3 }, + { result: { contentBase64: 'YWJj', bytesRead: 3 }, length: 2 } ]) { fsReadFileChunk.mockResolvedValue(result) - await expect(readRuntimeFileRange(localArgs, 0, 2)).rejects.toThrow('invalid CSV chunk') + await expect(readRuntimeFileRange(localArgs, 0, length)).rejects.toThrow('invalid CSV chunk') } }) }) @@ -156,7 +156,10 @@ describe('CSV file admission', () => { it('keeps small files editable and preserves existing dirty drafts', async () => { fsStat.mockResolvedValue({ ...snapshot, size: CSV_PAGED_PREVIEW_BYTES - 1 }) fsReadFile.mockResolvedValue({ content: 'a,b\n1,2', isBinary: false }) - expect((await readEditorCsvFileContent(localArgs)).csvPreview).toBeUndefined() + expect(await readEditorCsvFileContent(localArgs)).toEqual({ + content: 'a,b\n1,2', + isBinary: false + }) fsStat.mockClear() await readEditorCsvFileContent(localArgs, false) expect(fsStat).not.toHaveBeenCalled() diff --git a/src/renderer/src/runtime/runtime-file-search-bounds.test.ts b/src/renderer/src/runtime/runtime-file-search-bounds.test.ts index 271c35f3c57..993f8f76d74 100644 --- a/src/renderer/src/runtime/runtime-file-search-bounds.test.ts +++ b/src/renderer/src/runtime/runtime-file-search-bounds.test.ts @@ -1,6 +1,5 @@ import { describe, expect, it } from 'vitest' import { - createEmptyRuntimeFileSearchResult, getRuntimeFileSearchRejectedField, isRuntimeFileSearchTextWithinLimit } from './runtime-file-search-bounds' @@ -26,11 +25,4 @@ describe('runtime file search bounds', () => { }) ).toBe('query') }) - - it('returns metadata-only empty results for rejected searches', () => { - const result = createEmptyRuntimeFileSearchResult() - - expect(result).toEqual({ files: [], totalMatches: 0, truncated: false }) - expect(JSON.stringify(result)).not.toContain('secret') - }) }) diff --git a/src/renderer/src/runtime/runtime-path-existence-batch.test.ts b/src/renderer/src/runtime/runtime-path-existence-batch.test.ts index 43ab0453d14..dba99ddc034 100644 --- a/src/renderer/src/runtime/runtime-path-existence-batch.test.ts +++ b/src/renderer/src/runtime/runtime-path-existence-batch.test.ts @@ -59,6 +59,12 @@ it('old paired host retains scalar stats on that runtime and does not call the n ([args]) => args.method === 'files.stat' && args.selector === 'owner-a' ) ).toBe(true) + expect(runtimeEnvironmentCall.mock.calls.map(([args]) => args.params)).toEqual( + paths.map((path) => ({ + worktree: 'id:folder-1', + relativePath: path.slice('/folder/'.length) + })) + ) expect(fsPathExists).not.toHaveBeenCalled() }) it('new method missing after capability discovery falls back only for method_not_found', async () => { diff --git a/src/shared/agent-session-failure.test.ts b/src/shared/agent-session-failure.test.ts index 0a0501d7996..af01c0117d8 100644 --- a/src/shared/agent-session-failure.test.ts +++ b/src/shared/agent-session-failure.test.ts @@ -167,29 +167,57 @@ describe('reading a failure fact', () => { describe('reading all of a failure fact', () => { it('reads every part of a fact the host built, as it arrives off the wire', () => { - for (const fact of [ - agentSessionFailureFact('startFailed', { - refusal: { code: 'agent_session_conflict', details: { reason: 'claimConflicted' } } - }), - agentSessionFailureFact('providerRejected', { - detail: providerDiagnostic('Image type .bmp', 'person') - }), - agentSessionFailureFact('attachmentInvalid', { - attachment: { reason: 'tooLarge', limit: 5 * 1024 * 1024 } - }), - agentSessionFailureFact('providerRetrying', { - retry: readProviderRetry({ error: 'rate_limit', status: 429 }) - }), - agentSessionFailureFact('providerRetrying', { - retry: readProviderRetry({ - error: 'server_error', - status: 502, - attempt: 3, - maxRetries: 10 - }) - }) + for (const { fact, expected } of [ + { + fact: agentSessionFailureFact('startFailed', { + refusal: { code: 'agent_session_conflict', details: { reason: 'claimConflicted' } } + }), + expected: { + kind: 'startFailed', + refusal: { code: 'agent_session_conflict', details: { reason: 'claimConflicted' } } + } + }, + { + fact: agentSessionFailureFact('providerRejected', { + detail: providerDiagnostic('Image type .bmp', 'person') + }), + expected: { + kind: 'providerRejected', + detail: { text: 'Image type .bmp', audience: 'person' } + } + }, + { + fact: agentSessionFailureFact('attachmentInvalid', { + attachment: { reason: 'tooLarge', limit: 5 * 1024 * 1024 } + }), + expected: { + kind: 'attachmentInvalid', + attachment: { reason: 'tooLarge', limit: 5 * 1024 * 1024 } + } + }, + { + fact: agentSessionFailureFact('providerRetrying', { + retry: readProviderRetry({ error: 'rate_limit', status: 429 }) + }), + expected: { kind: 'providerRetrying', retry: { error: 'rate_limit', status: 429 } } + }, + { + fact: agentSessionFailureFact('providerRetrying', { + retry: readProviderRetry({ + error: 'server_error', + status: 502, + attempt: 3, + maxRetries: 10 + }) + }), + expected: { + kind: 'providerRetrying', + retry: { error: 'server_error', status: 502, attempt: 3, maxRetries: 10 } + } + } ]) { - expect(readWholeAgentSessionFailureFact(JSON.parse(JSON.stringify(fact)))).toEqual(fact) + expect(fact).toEqual(expected) + expect(readWholeAgentSessionFailureFact(JSON.parse(JSON.stringify(fact)))).toEqual(expected) } // Every reason a refusal can name, on the code that names it. for (const [code, reasons] of Object.entries(AGENT_SESSION_REFUSAL_REASONS)) {