Files
orca/src/shared/terminal-input.test.ts
T

135 lines
4.7 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from 'vitest'
import {
TERMINAL_INPUT_CHUNK_MAX_BYTES,
TERMINAL_INPUT_TOO_LARGE_ERROR,
assertTerminalInputWithinLimit,
getTerminalInputByteLength,
iterateTerminalInputChunks,
isTerminalInputTooLarge,
isTerminalInputTooLargeWithDeferredMeasurement,
isTerminalInputTooLargeWithYield,
splitTerminalInputChunks
} from './terminal-input'
import { CLIPBOARD_TEXT_MEASURE_YIELD_CODE_UNITS } from './clipboard-text'
afterEach(() => {
vi.useRealTimers()
vi.restoreAllMocks()
})
describe('terminal input bounds', () => {
it('keeps small terminal input as one chunk', () => {
expect(splitTerminalInputChunks('npm test')).toEqual(['npm test'])
})
it('splits by UTF-8 bytes without splitting surrogate pairs', () => {
const chunks = splitTerminalInputChunks('ab😀cd', 4)
expect(chunks).toEqual(['ab', '😀', 'cd'])
expect(chunks.join('')).toBe('ab😀cd')
})
it('uses 16KB as the default terminal input chunk budget', () => {
const chunks = splitTerminalInputChunks('x'.repeat(TERMINAL_INPUT_CHUNK_MAX_BYTES + 1))
expect(chunks).toEqual(['x'.repeat(TERMINAL_INPUT_CHUNK_MAX_BYTES), 'x'])
})
it('iterates chunks lazily without prebuilding every terminal input chunk', () => {
const chunks = iterateTerminalInputChunks('abcdefghij', 4)
expect(chunks.next()).toEqual({ done: false, value: 'abcd' })
expect(chunks.next()).toEqual({ done: false, value: 'efgh' })
expect(chunks.next()).toEqual({ done: false, value: 'ij' })
expect(chunks.next()).toEqual({ done: true, value: undefined })
})
it('does not scan the full payload before yielding the first chunk', () => {
const codePointAt = vi.spyOn(String.prototype, 'codePointAt')
const text = 'x'.repeat(128)
const chunks = iterateTerminalInputChunks(text, 8)
expect(chunks.next()).toEqual({ done: false, value: 'x'.repeat(8) })
expect(codePointAt.mock.calls.length).toBeLessThan(text.length)
})
it('measures UTF-8 bytes for terminal input without using UTF-16 length', () => {
expect(getTerminalInputByteLength('a😀')).toBe(5)
})
it('keeps a single multibyte terminal character intact when the byte cap is smaller', () => {
const chunks = splitTerminalInputChunks('😀a', 1)
expect(chunks).toEqual(['😀', 'a'])
expect(chunks.join('')).toBe('😀a')
})
it('rejects oversized terminal input with a metadata-only error', () => {
const secret = 'terminal-secret-token'
const payload = [secret, 'payload'].join('')
expect(() => assertTerminalInputWithinLimit(payload, 4)).toThrow(TERMINAL_INPUT_TOO_LARGE_ERROR)
expect(() => assertTerminalInputWithinLimit(payload, 4)).not.toThrow(secret)
})
it('rejects multibyte oversized terminal input at the byte boundary', () => {
expect(isTerminalInputTooLarge('😀'.repeat(3), 5)).toBe(true)
expect(() => assertTerminalInputWithinLimit('😀'.repeat(3), 5)).toThrow(
TERMINAL_INPUT_TOO_LARGE_ERROR
)
})
it('rejects terminal input whose string length already exceeds the byte limit without scanning', () => {
const codePointAt = vi.spyOn(String.prototype, 'codePointAt')
expect(isTerminalInputTooLarge('x'.repeat(6), 5)).toBe(true)
expect(() => assertTerminalInputWithinLimit('x'.repeat(6), 5)).toThrow(
TERMINAL_INPUT_TOO_LARGE_ERROR
)
expect(codePointAt).not.toHaveBeenCalled()
})
it('yields while measuring accepted large terminal input', async () => {
vi.useFakeTimers()
const text = 'é'.repeat(CLIPBOARD_TEXT_MEASURE_YIELD_CODE_UNITS + 1)
const result = isTerminalInputTooLargeWithYield(text, text.length * 3)
let settled = false
void result.then(() => {
settled = true
})
await Promise.resolve()
expect(settled).toBe(false)
await vi.advanceTimersByTimeAsync(0)
await expect(result).resolves.toBe(false)
})
it('keeps deferred terminal input validation synchronous for small or obvious oversized input', () => {
expect(isTerminalInputTooLargeWithDeferredMeasurement('npm test')).toBe(false)
expect(isTerminalInputTooLargeWithDeferredMeasurement('x'.repeat(6), 5)).toBe(true)
})
it('returns a pending validation for accepted large terminal input', async () => {
vi.useFakeTimers()
const text = 'é'.repeat(CLIPBOARD_TEXT_MEASURE_YIELD_CODE_UNITS + 1)
const result = isTerminalInputTooLargeWithDeferredMeasurement(text, text.length * 3)
let settled = false
void Promise.resolve(result).then(() => {
settled = true
})
await Promise.resolve()
expect(result).toBeInstanceOf(Promise)
expect(settled).toBe(false)
await vi.advanceTimersByTimeAsync(0)
await expect(result).resolves.toBe(false)
})
})