basant307/AI_Governance_Project
048
1/**2 * @license3 * Copyright 2026 Qwen Team4 * SPDX-License-Identifier: Apache-2.05 */6 7import { describe, it, expect, beforeEach, afterEach } from 'vitest';8import {9 startEarlyInputCapture,10 stopEarlyInputCapture,11 getAndClearCapturedInput,12 stopAndGetCapturedInput,13 hasCapturedInput,14 resetCaptureState,15} from './earlyInputCapture.js';16import { PassThrough } from 'node:stream';17 18describe('earlyInputCapture', () => {19 let mockStdin: PassThrough;20 let originalStdin: typeof process.stdin;21 let originalIsTTY: boolean;22 23 beforeEach(() => {24 resetCaptureState();25 26 // Save original stdin27 originalStdin = process.stdin;28 originalIsTTY = process.stdin.isTTY ?? false;29 30 // Create mock stdin31 mockStdin = new PassThrough();32 Object.defineProperty(process, 'stdin', {33 value: mockStdin,34 writable: true,35 configurable: true,36 });37 Object.defineProperty(process.stdin, 'isTTY', {38 value: true,39 writable: true,40 configurable: true,41 });42 43 delete process.env['QWEN_CODE_DISABLE_EARLY_CAPTURE'];44 });45 46 afterEach(() => {47 resetCaptureState();48 49 // Restore original stdin50 Object.defineProperty(process, 'stdin', {51 value: originalStdin,52 writable: true,53 configurable: true,54 });55 Object.defineProperty(process.stdin, 'isTTY', {56 value: originalIsTTY,57 writable: true,58 configurable: true,59 });60 });61 62 describe('capture lifecycle', () => {63 it('should start and stop capture correctly', () => {64 startEarlyInputCapture();65 expect(hasCapturedInput()).toBe(false);66 67 mockStdin.write(Buffer.from('a'));68 expect(hasCapturedInput()).toBe(true);69 70 stopEarlyInputCapture();71 const input = getAndClearCapturedInput();72 expect(input.toString()).toBe('a');73 });74 75 it('should not capture after stop', () => {76 startEarlyInputCapture();77 mockStdin.write(Buffer.from('a'));78 stopEarlyInputCapture();79 mockStdin.write(Buffer.from('b'));80 81 const input = getAndClearCapturedInput();82 expect(input.toString()).toBe('a');83 });84 85 it('should not start capture if not TTY', () => {86 Object.defineProperty(process.stdin, 'isTTY', { value: false });87 startEarlyInputCapture();88 mockStdin.write(Buffer.from('a'));89 stopEarlyInputCapture();90 91 expect(hasCapturedInput()).toBe(false);92 });93 94 it('should not start capture twice', () => {95 startEarlyInputCapture();96 startEarlyInputCapture(); // Second call should be ignored97 98 mockStdin.write(Buffer.from('a'));99 stopEarlyInputCapture();100 101 const input = getAndClearCapturedInput();102 expect(input.toString()).toBe('a');103 });104 105 it('stopAndGetCapturedInput should atomically stop and return input', () => {106 startEarlyInputCapture();107 mockStdin.write(Buffer.from('hello'));108 109 const input = stopAndGetCapturedInput();110 expect(input.toString()).toBe('hello');111 112 // Further writes should not be captured113 mockStdin.write(Buffer.from('world'));114 expect(hasCapturedInput()).toBe(false);115 });116 });117 118 describe('terminal response filtering', () => {119 it('should filter DEC private mode responses (ESC [ ?)', () => {120 startEarlyInputCapture();121 // DEC private mode response: ESC [ ? 1 0 0 4 h122 mockStdin.write(Buffer.from('\x1b[?1004h'));123 stopEarlyInputCapture();124 125 const input = getAndClearCapturedInput();126 expect(input.length).toBe(0);127 });128 129 it('should filter DA2 responses (ESC [ >)', () => {130 startEarlyInputCapture();131 // DA2 response: ESC [ > 0 ; 9 5 ; 0 c132 mockStdin.write(Buffer.from('\x1b[>0;95;0c'));133 stopEarlyInputCapture();134 135 const input = getAndClearCapturedInput();136 expect(input.length).toBe(0);137 });138 139 it('should filter OSC sequences (ESC ])', () => {140 startEarlyInputCapture();141 // OSC sequence: ESC ] 0 ; title BEL142 mockStdin.write(Buffer.from('\x1b]0;window title\x07'));143 stopEarlyInputCapture();144 145 const input = getAndClearCapturedInput();146 expect(input.length).toBe(0);147 });148 149 it('should filter DCS sequences (ESC P)', () => {150 startEarlyInputCapture();151 // DCS sequence: ESC P ... ST152 mockStdin.write(Buffer.from('\x1bP$data\x1b\\'));153 stopEarlyInputCapture();154 155 const input = getAndClearCapturedInput();156 expect(input.length).toBe(0);157 });158 159 it('should keep user input mixed with terminal responses', () => {160 startEarlyInputCapture();161 // Mix of user input and terminal response162 mockStdin.write(Buffer.from('a\x1b[?1004hb\x1b]0;title\x07c'));163 stopEarlyInputCapture();164 165 const input = getAndClearCapturedInput();166 expect(input.toString()).toBe('abc');167 });168 169 it('should keep arrow key sequences (user input)', () => {170 startEarlyInputCapture();171 // Arrow up: ESC [ A (this is a user input, not terminal response)172 mockStdin.write(Buffer.from('\x1b[A'));173 stopEarlyInputCapture();174 175 const input = getAndClearCapturedInput();176 // Arrow key sequence should be kept (it's user input)177 expect(input.toString()).toBe('\x1b[A');178 });179 180 it('should keep function key sequences (user input)', () => {181 startEarlyInputCapture();182 // F1: ESC O P183 mockStdin.write(Buffer.from('\x1bOP'));184 stopEarlyInputCapture();185 186 const input = getAndClearCapturedInput();187 expect(input.toString()).toBe('\x1bOP');188 });189 190 it('should filter terminal responses split across chunks', () => {191 startEarlyInputCapture();192 mockStdin.write(Buffer.from('\x1b[?1004'));193 mockStdin.write(Buffer.from('h'));194 stopEarlyInputCapture();195 196 const input = getAndClearCapturedInput();197 expect(input.length).toBe(0);198 });199 200 it('should keep user input around split terminal responses', () => {201 startEarlyInputCapture();202 mockStdin.write(Buffer.from('a\x1b[?100'));203 mockStdin.write(Buffer.from('4hbc'));204 stopEarlyInputCapture();205 206 const input = getAndClearCapturedInput();207 expect(input.toString()).toBe('abc');208 });209 210 it('should filter terminal responses split at ESC[ prefix', () => {211 startEarlyInputCapture();212 mockStdin.write(Buffer.from('\x1b['));213 mockStdin.write(Buffer.from('?1004h'));214 stopEarlyInputCapture();215 216 const input = getAndClearCapturedInput();217 expect(input.length).toBe(0);218 });219 220 it('should filter terminal responses split at ESC prefix', () => {221 startEarlyInputCapture();222 mockStdin.write(Buffer.from('\x1b'));223 mockStdin.write(Buffer.from('[?1004h'));224 stopEarlyInputCapture();225 226 const input = getAndClearCapturedInput();227 expect(input.length).toBe(0);228 });229 230 it('should drop incomplete DEC private response on capture end', () => {231 startEarlyInputCapture();232 mockStdin.write(Buffer.from('\x1b[?1004'));233 stopEarlyInputCapture();234 235 const input = getAndClearCapturedInput();236 expect(input.length).toBe(0);237 });238 239 it('should drop incomplete OSC sequence on capture end', () => {240 startEarlyInputCapture();241 mockStdin.write(Buffer.from('\x1b]0;title'));242 stopEarlyInputCapture();243 244 const input = getAndClearCapturedInput();245 expect(input.length).toBe(0);246 });247 248 it('should keep arrow key sequence split across chunks', () => {249 startEarlyInputCapture();250 mockStdin.write(Buffer.from('\x1b['));251 mockStdin.write(Buffer.from('A'));252 stopEarlyInputCapture();253 254 const input = getAndClearCapturedInput();255 expect(input.toString()).toBe('\x1b[A');256 });257 258 it('should drop incomplete ESC[ prefix on capture end', () => {259 startEarlyInputCapture();260 mockStdin.write(Buffer.from('\x1b['));261 stopEarlyInputCapture();262 263 const input = getAndClearCapturedInput();264 expect(input.toString()).toBe('');265 });266 267 it('should replay standalone ESC on capture end', () => {268 startEarlyInputCapture();269 mockStdin.write(Buffer.from('\x1b'));270 stopEarlyInputCapture();271 272 const input = getAndClearCapturedInput();273 expect(input.toString()).toBe('\x1b');274 });275 });276 277 describe('UTF-8 handling', () => {278 it('should capture simple ASCII characters', () => {279 startEarlyInputCapture();280 mockStdin.write(Buffer.from('abc'));281 stopEarlyInputCapture();282 283 const input = getAndClearCapturedInput();284 expect(input.toString()).toBe('abc');285 });286 287 it('should capture UTF-8 multibyte characters', () => {288 startEarlyInputCapture();289 mockStdin.write(Buffer.from('你好世界'));290 stopEarlyInputCapture();291 292 const input = getAndClearCapturedInput();293 expect(input.toString()).toBe('你好世界');294 });295 296 it('should capture emoji', () => {297 startEarlyInputCapture();298 mockStdin.write(Buffer.from('👋🎉'));299 stopEarlyInputCapture();300 301 const input = getAndClearCapturedInput();302 expect(input.toString()).toBe('👋🎉');303 });304 });305 306 describe('edge cases', () => {307 it('should handle empty input', () => {308 startEarlyInputCapture();309 stopEarlyInputCapture();310 311 const input = getAndClearCapturedInput();312 expect(input.length).toBe(0);313 });314 315 it('should clear captured input after getAndClearCapturedInput', () => {316 startEarlyInputCapture();317 mockStdin.write(Buffer.from('test'));318 stopEarlyInputCapture();319 320 const input1 = getAndClearCapturedInput();321 expect(input1.toString()).toBe('test');322 323 const input2 = getAndClearCapturedInput();324 expect(input2.length).toBe(0);325 });326 327 it('should skip when QWEN_CODE_DISABLE_EARLY_CAPTURE is set', () => {328 process.env['QWEN_CODE_DISABLE_EARLY_CAPTURE'] = '1';329 startEarlyInputCapture();330 mockStdin.write(Buffer.from('a'));331 stopEarlyInputCapture();332 333 expect(hasCapturedInput()).toBe(false);334 });335 336 it('should limit buffer size', () => {337 startEarlyInputCapture();338 339 // Write more than 64KB340 const largeData = Buffer.alloc(100 * 1024, 'a');341 mockStdin.write(largeData);342 stopEarlyInputCapture();343 344 const input = getAndClearCapturedInput();345 // Should be truncated to 64KB346 expect(input.length).toBeLessThanOrEqual(64 * 1024);347 });348 });349});350 