Files
wehub-resource-sync 0d3cb498a3
Deploy local.promptfoo.app / Deploy to Cloudflare Pages (push) Waiting to run
Test and Publish Multi-arch Docker Image / test (push) Waiting to run
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-amd64 platform:linux/amd64 runner:ubuntu-latest]) (push) Blocked by required conditions
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-arm64 platform:linux/arm64 runner:ubuntu-24.04-arm]) (push) Blocked by required conditions
Test and Publish Multi-arch Docker Image / merge-docker-digests (push) Blocked by required conditions
Test and Publish Multi-arch Docker Image / Attest Multi-arch Image (push) Blocked by required conditions
Validate Renovate Config / Validate Renovate Configuration (push) Waiting to run
CI / Shell Format Check (push) Has been cancelled
CI / Check Ruby (3.4) (push) Has been cancelled
CI / CI Config (push) Has been cancelled
CI / Test on Node ${{ matrix.node }} and ${{ matrix.os }}${{ matrix.shard && format(' (shard {0}/3)', matrix.shard) || '' }} (push) Has been cancelled
CI / Build on Node ${{ matrix.node }} (push) Has been cancelled
CI / Style Check (push) Has been cancelled
CI / Generate Assets (push) Has been cancelled
CI / Check Python (3.14) (push) Has been cancelled
CI / Check Python (3.9) (push) Has been cancelled
CI / Build Docs (push) Has been cancelled
CI / Code Scan Action (push) Has been cancelled
CI / Site tests (push) Has been cancelled
CI / webui tests (push) Has been cancelled
CI / Run Integration Tests (push) Has been cancelled
CI / Run Smoke Tests (push) Has been cancelled
CI / Go Tests (push) Has been cancelled
CI / Share Test (push) Has been cancelled
CI / Redteam (Production API) (push) Has been cancelled
CI / Redteam (Staging API) (push) Has been cancelled
CI / GitHub Actions Lint (push) Has been cancelled
CI / Check Ruby (3.0) (push) Has been cancelled
release-please / release-please (push) Has been cancelled
release-please / build (push) Has been cancelled
release-please / publish-npm (push) Has been cancelled
release-please / publish-npm-backfill (push) Has been cancelled
release-please / docker (push) Has been cancelled
release-please / publish-code-scan-action (push) Has been cancelled
release-please / attest-code-scan-action (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:24:08 +08:00

137 lines
4.3 KiB
TypeScript

import { EventEmitter } from 'events';
import type { ChildProcess } from 'child_process';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { mockProcessEnv } from '../../util/utils';
const mocks = vi.hoisted(() => ({
spawn: vi.fn(),
}));
vi.mock('child_process', async (importOriginal) => {
const actual = await importOriginal<typeof import('child_process')>();
return {
...actual,
spawn: mocks.spawn,
};
});
import {
startFilesystemMcpServer,
waitForFilesystemMcpServerReady,
} from '../../../src/codeScan/mcp/filesystem';
class FakeChildProcess extends EventEmitter {
exitCode: number | null = null;
killed = false;
kill = vi.fn();
pid = 1234;
signalCode: NodeJS.Signals | null = null;
stderr = new EventEmitter();
}
function createFakeProcess(): ChildProcess & { stderr: EventEmitter } {
return new FakeChildProcess() as unknown as ChildProcess & { stderr: EventEmitter };
}
describe('filesystem MCP server management', () => {
const originalEnv = { ...process.env };
let restoreEnv: () => void;
beforeEach(() => {
vi.useFakeTimers();
vi.resetAllMocks();
restoreEnv = mockProcessEnv(originalEnv, { clear: true });
});
afterEach(() => {
restoreEnv();
vi.useRealTimers();
vi.clearAllMocks();
});
it('strips npm before config when spawning the filesystem MCP server', () => {
const restoreNpmConfig = mockProcessEnv({
NPM_CONFIG_BEFORE: '2026-03-29T00:00:00.000Z',
npm_config_before: '2026-03-29T00:00:00.000Z',
});
try {
mocks.spawn.mockReturnValue(createFakeProcess());
startFilesystemMcpServer(process.cwd());
const spawnOptions = mocks.spawn.mock.calls[0]?.[2];
expect(spawnOptions?.env?.NPM_CONFIG_BEFORE).toBeUndefined();
expect(spawnOptions?.env?.npm_config_before).toBeUndefined();
} finally {
restoreNpmConfig();
}
});
it('resolves when the filesystem MCP server prints its ready marker', async () => {
const mcpProcess = createFakeProcess();
const ready = waitForFilesystemMcpServerReady(mcpProcess);
mcpProcess.stderr.emit('data', Buffer.from('Secure MCP Filesystem Server running on stdio\n'));
await expect(ready).resolves.toBeUndefined();
});
it('resolves when the ready marker is split across stderr chunks', async () => {
const mcpProcess = createFakeProcess();
const ready = waitForFilesystemMcpServerReady(mcpProcess);
mcpProcess.stderr.emit('data', Buffer.from('Secure MCP Filesystem Server '));
mcpProcess.stderr.emit('data', Buffer.from('running on stdio\n'));
await expect(ready).resolves.toBeUndefined();
});
it('rejects when the filesystem MCP server exits before it is ready', async () => {
const mcpProcess = createFakeProcess();
const ready = waitForFilesystemMcpServerReady(mcpProcess);
mcpProcess.emit('exit', 1, null);
await expect(ready).rejects.toThrow('Filesystem MCP server exited before ready: code 1');
});
it('rejects immediately when the process has already exited', async () => {
const mcpProcess = createFakeProcess();
Object.defineProperty(mcpProcess, 'exitCode', { value: 1 });
await expect(waitForFilesystemMcpServerReady(mcpProcess)).rejects.toThrow(
'Filesystem MCP server exited before ready: code 1',
);
});
it('rejects immediately when the process was already killed', async () => {
const mcpProcess = createFakeProcess();
Object.defineProperty(mcpProcess, 'killed', { value: true });
await expect(waitForFilesystemMcpServerReady(mcpProcess)).rejects.toThrow(
'Filesystem MCP server exited before ready: unknown reason',
);
});
it('rejects when stderr is unavailable', async () => {
const mcpProcess = createFakeProcess();
Object.defineProperty(mcpProcess, 'stderr', { value: null });
await expect(waitForFilesystemMcpServerReady(mcpProcess)).rejects.toThrow(
'Filesystem MCP server stderr pipe unavailable',
);
});
it('rejects when the filesystem MCP server readiness times out', async () => {
const mcpProcess = createFakeProcess();
const ready = waitForFilesystemMcpServerReady(mcpProcess, 1000);
const expectation = expect(ready).rejects.toThrow(
'Timed out waiting for filesystem MCP server to be ready after 1000ms',
);
await vi.advanceTimersByTimeAsync(1000);
await expectation;
});
});