* feat(docs): Add analysis reports for issues #514, #517, #520, #527, and #532 - Issue #514: Documented analysis of orphaned observer session files, including root cause, evidence, and recommended fixes. - Issue #517: Analyzed PowerShell escaping issues in cleanupOrphanedProcesses() on Windows, with recommended fixes using WMIC. - Issue #520: Confirmed resolution of stuck messages issue through architectural changes to a claim-and-delete pattern. - Issue #527: Identified detection failure of uv on Apple Silicon Macs with Homebrew installation, proposed path updates for detection. - Issue #532: Analyzed memory leak issues in SessionManager, detailing session cleanup and conversationHistory growth concerns, with recommended fixes. * fix: address GitHub issues #511, #517, #527, #531 - #511: Add gemini-3-flash model to GeminiAgent (type, RPM limits, validation) - #517: Replace PowerShell with WMIC for Windows process management (fixes Git Bash/WSL) - #527: Add Apple Silicon Homebrew paths for bun and uv detection - #531: Remove duplicate type definitions from export-memories.ts using bridge file 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(docs): Add detailed reports for issues #511 and #531 addressing model validation and type duplication * test: add regression tests for PR #542 fixes Adds comprehensive regression tests for all 4 issues addressed in PR #542: - #511: Add gemini-3-flash model tests to verify model acceptance and rate limiting - #517: Add WMIC parsing tests for Windows process enumeration (23 tests) - #527: Add Apple Silicon Homebrew path tests for bun/uv detection (18 tests) - #531: Add export types tests to validate type interfaces (12 tests) Total: 53 new tests, all passing. Addresses PR review feedback requesting test coverage. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,238 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
|
||||
|
||||
/**
|
||||
* Tests for WMIC output parsing logic used in Windows process enumeration.
|
||||
*
|
||||
* This tests the parsing behavior directly since mocking promisified exec
|
||||
* is unreliable across module boundaries. The parsing logic matches exactly
|
||||
* what's in ProcessManager.getChildProcesses().
|
||||
*/
|
||||
|
||||
// Extract the parsing logic from ProcessManager for direct testing
|
||||
// This matches the implementation in src/services/infrastructure/ProcessManager.ts lines 93-100
|
||||
function parseWmicOutput(stdout: string): number[] {
|
||||
return stdout
|
||||
.trim()
|
||||
.split('\n')
|
||||
.map(line => {
|
||||
const match = line.match(/ProcessId=(\d+)/i);
|
||||
return match ? parseInt(match[1], 10) : NaN;
|
||||
})
|
||||
.filter(n => !isNaN(n) && Number.isInteger(n) && n > 0);
|
||||
}
|
||||
|
||||
// Validate parent PID - matches ProcessManager.getChildProcesses() lines 85-88
|
||||
function isValidParentPid(parentPid: number): boolean {
|
||||
return Number.isInteger(parentPid) && parentPid > 0;
|
||||
}
|
||||
|
||||
describe('WMIC output parsing (Windows)', () => {
|
||||
describe('parseWmicOutput - ProcessId format parsing', () => {
|
||||
it('should parse ProcessId=12345 format correctly', () => {
|
||||
const stdout = 'ProcessId=12345\r\nProcessId=67890\r\n';
|
||||
|
||||
const result = parseWmicOutput(stdout);
|
||||
|
||||
expect(result).toEqual([12345, 67890]);
|
||||
});
|
||||
|
||||
it('should parse single PID from WMIC output', () => {
|
||||
const stdout = 'ProcessId=54321\r\n';
|
||||
|
||||
const result = parseWmicOutput(stdout);
|
||||
|
||||
expect(result).toEqual([54321]);
|
||||
});
|
||||
|
||||
it('should handle WMIC output with mixed case', () => {
|
||||
// WMIC output can vary in case on different Windows versions
|
||||
const stdout = 'PROCESSID=11111\r\nprocessid=22222\r\nProcessId=33333\r\n';
|
||||
|
||||
const result = parseWmicOutput(stdout);
|
||||
|
||||
expect(result).toEqual([11111, 22222, 33333]);
|
||||
});
|
||||
|
||||
it('should handle empty WMIC output', () => {
|
||||
const stdout = '';
|
||||
|
||||
const result = parseWmicOutput(stdout);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle WMIC output with only whitespace', () => {
|
||||
const stdout = ' \r\n \r\n';
|
||||
|
||||
const result = parseWmicOutput(stdout);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should filter invalid PIDs from WMIC output', () => {
|
||||
const stdout = 'ProcessId=12345\r\nProcessId=invalid\r\nProcessId=67890\r\n';
|
||||
|
||||
const result = parseWmicOutput(stdout);
|
||||
|
||||
expect(result).toEqual([12345, 67890]);
|
||||
});
|
||||
|
||||
it('should filter negative PIDs from WMIC output', () => {
|
||||
// Negative PIDs won't match the regex /ProcessId=(\d+)/i (only digits)
|
||||
const stdout = 'ProcessId=12345\r\nProcessId=-1\r\nProcessId=67890\r\n';
|
||||
|
||||
const result = parseWmicOutput(stdout);
|
||||
|
||||
expect(result).toEqual([12345, 67890]);
|
||||
});
|
||||
|
||||
it('should filter zero PIDs from WMIC output', () => {
|
||||
// Zero is filtered out by the n > 0 check
|
||||
const stdout = 'ProcessId=0\r\nProcessId=12345\r\n';
|
||||
|
||||
const result = parseWmicOutput(stdout);
|
||||
|
||||
expect(result).toEqual([12345]);
|
||||
});
|
||||
|
||||
it('should handle WMIC output with extra lines and noise', () => {
|
||||
const stdout = '\r\n\r\nProcessId=12345\r\n\r\nSome other output\r\nProcessId=67890\r\n\r\n';
|
||||
|
||||
const result = parseWmicOutput(stdout);
|
||||
|
||||
expect(result).toEqual([12345, 67890]);
|
||||
});
|
||||
|
||||
it('should handle Windows line endings (CRLF)', () => {
|
||||
const stdout = 'ProcessId=111\r\nProcessId=222\r\nProcessId=333\r\n';
|
||||
|
||||
const result = parseWmicOutput(stdout);
|
||||
|
||||
expect(result).toEqual([111, 222, 333]);
|
||||
});
|
||||
|
||||
it('should handle Unix line endings (LF)', () => {
|
||||
const stdout = 'ProcessId=111\nProcessId=222\nProcessId=333\n';
|
||||
|
||||
const result = parseWmicOutput(stdout);
|
||||
|
||||
expect(result).toEqual([111, 222, 333]);
|
||||
});
|
||||
|
||||
it('should handle lines with extra equals signs', () => {
|
||||
const stdout = 'ProcessId=12345\r\nSomeOther=value=with=equals\r\nProcessId=67890\r\n';
|
||||
|
||||
const result = parseWmicOutput(stdout);
|
||||
|
||||
expect(result).toEqual([12345, 67890]);
|
||||
});
|
||||
|
||||
it('should handle very large PIDs', () => {
|
||||
// Windows PIDs can be large but are still 32-bit integers
|
||||
const stdout = 'ProcessId=2147483647\r\n';
|
||||
|
||||
const result = parseWmicOutput(stdout);
|
||||
|
||||
expect(result).toEqual([2147483647]);
|
||||
});
|
||||
|
||||
it('should handle typical WMIC list format output', () => {
|
||||
// Real WMIC output often has blank lines and extra spacing
|
||||
const stdout = `
|
||||
|
||||
ProcessId=1234
|
||||
|
||||
|
||||
ProcessId=5678
|
||||
|
||||
`;
|
||||
|
||||
const result = parseWmicOutput(stdout);
|
||||
|
||||
expect(result).toEqual([1234, 5678]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parent PID validation', () => {
|
||||
it('should reject zero PID', () => {
|
||||
expect(isValidParentPid(0)).toBe(false);
|
||||
});
|
||||
|
||||
it('should reject negative PID', () => {
|
||||
expect(isValidParentPid(-1)).toBe(false);
|
||||
expect(isValidParentPid(-100)).toBe(false);
|
||||
});
|
||||
|
||||
it('should reject NaN', () => {
|
||||
expect(isValidParentPid(NaN)).toBe(false);
|
||||
});
|
||||
|
||||
it('should reject non-integer (float)', () => {
|
||||
expect(isValidParentPid(1.5)).toBe(false);
|
||||
expect(isValidParentPid(100.1)).toBe(false);
|
||||
});
|
||||
|
||||
it('should reject Infinity', () => {
|
||||
expect(isValidParentPid(Infinity)).toBe(false);
|
||||
expect(isValidParentPid(-Infinity)).toBe(false);
|
||||
});
|
||||
|
||||
it('should accept valid positive integer PID', () => {
|
||||
expect(isValidParentPid(1)).toBe(true);
|
||||
expect(isValidParentPid(1000)).toBe(true);
|
||||
expect(isValidParentPid(12345)).toBe(true);
|
||||
expect(isValidParentPid(2147483647)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getChildProcesses platform behavior', () => {
|
||||
const originalPlatform = process.platform;
|
||||
|
||||
afterEach(() => {
|
||||
Object.defineProperty(process, 'platform', {
|
||||
value: originalPlatform,
|
||||
writable: true,
|
||||
configurable: true
|
||||
});
|
||||
});
|
||||
|
||||
it('should return empty array on non-Windows platforms (darwin)', async () => {
|
||||
Object.defineProperty(process, 'platform', {
|
||||
value: 'darwin',
|
||||
writable: true,
|
||||
configurable: true
|
||||
});
|
||||
|
||||
// Import fresh to get updated platform value
|
||||
const { getChildProcesses } = await import('../../src/services/infrastructure/ProcessManager.js');
|
||||
|
||||
const result = await getChildProcesses(1000);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return empty array on non-Windows platforms (linux)', async () => {
|
||||
Object.defineProperty(process, 'platform', {
|
||||
value: 'linux',
|
||||
writable: true,
|
||||
configurable: true
|
||||
});
|
||||
|
||||
const { getChildProcesses } = await import('../../src/services/infrastructure/ProcessManager.js');
|
||||
|
||||
const result = await getChildProcesses(1000);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return empty array for invalid parent PID regardless of platform', async () => {
|
||||
// Even on Windows, invalid parent PIDs should be rejected before exec
|
||||
const { getChildProcesses } = await import('../../src/services/infrastructure/ProcessManager.js');
|
||||
|
||||
expect(await getChildProcesses(0)).toEqual([]);
|
||||
expect(await getChildProcesses(-1)).toEqual([]);
|
||||
expect(await getChildProcesses(NaN)).toEqual([]);
|
||||
expect(await getChildProcesses(1.5)).toEqual([]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user