test(hooks): harden cross-platform MCP coverage

- replace the Windows-brittle chmod read-error setup with a directory-based failure case

- force the browser installed-copy regression through the non-global WebSocket path
This commit is contained in:
Tam Nhu Tran
2026-04-13 13:10:05 -04:00
parent d30f5803a7
commit 3103f3f293
2 changed files with 14 additions and 17 deletions
+4 -8
View File
@@ -871,12 +871,10 @@ describe('Image Analyzer Hook', () => {
}); });
it('should output valid JSON structure on file read error', async () => { it('should output valid JSON structure on file read error', async () => {
// Create and immediately delete file to trigger error // Use a directory with an analyzable extension so readFileSync fails
// consistently across POSIX and Windows.
const errorPath = path.join(TEST_DIR, 'error-test.png'); const errorPath = path.join(TEST_DIR, 'error-test.png');
createTestPng(errorPath); fs.mkdirSync(errorPath);
// Make file unreadable (simulate permission error)
fs.chmodSync(errorPath, 0o000);
const result = await invokeHookAsync( const result = await invokeHookAsync(
{ {
@@ -886,9 +884,7 @@ describe('Image Analyzer Hook', () => {
{ CCS_IMAGE_ANALYSIS_ENABLED: '1', CCS_PROFILE_TYPE: 'cliproxy' } { CCS_IMAGE_ANALYSIS_ENABLED: '1', CCS_PROFILE_TYPE: 'cliproxy' }
); );
// Restore permissions and cleanup fs.rmSync(errorPath, { recursive: true, force: true });
fs.chmodSync(errorPath, 0o644);
fs.unlinkSync(errorPath);
// Should output error in JSON format // Should output error in JSON format
expect(result.code).toBe(2); expect(result.code).toBe(2);
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, it } from 'bun:test'; import { afterEach, describe, expect, it } from 'bun:test';
import { spawn } from 'child_process'; import { spawn } from 'child_process';
import { cpSync, mkdirSync, mkdtempSync, rmSync } from 'node:fs'; import { cpSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
import * as http from 'node:http'; import * as http from 'node:http';
import { WebSocketServer } from 'ws'; import { WebSocketServer } from 'ws';
import { tmpdir } from 'node:os'; import { tmpdir } from 'node:os';
@@ -522,17 +522,18 @@ describe('ccs-browser MCP server', () => {
} }
}); });
it('works from an installed copy when ws is absent but undici is available via NODE_PATH', async () => { it('works from an installed copy when global WebSocket is unavailable and NODE_PATH supplies package dependencies', async () => {
const installDir = mkdtempSync(join(tmpdir(), 'ccs-browser-installed-copy-')); const installDir = mkdtempSync(join(tmpdir(), 'ccs-browser-installed-copy-'));
const installedServerPath = join(installDir, 'ccs-browser-server.cjs'); const installedServerPath = join(installDir, 'ccs-browser-server.cjs');
const nodeModulesDir = join(installDir, 'node_modules'); const bootstrapServerPath = join(installDir, 'bootstrap.cjs');
try { try {
cpSync(bundledServerPath, installedServerPath); cpSync(bundledServerPath, installedServerPath);
mkdirSync(nodeModulesDir, { recursive: true }); writeFileSync(
cpSync(join(process.cwd(), 'node_modules', 'undici'), join(nodeModulesDir, 'undici'), { bootstrapServerPath,
recursive: true, 'delete globalThis.WebSocket;\nrequire("./ccs-browser-server.cjs");\n',
}); 'utf8'
);
const responses = await runMcpRequests( const responses = await runMcpRequests(
[{ id: 'page-1', title: 'Installed Copy', currentUrl: 'https://example.com/' }], [{ id: 'page-1', title: 'Installed Copy', currentUrl: 'https://example.com/' }],
@@ -545,9 +546,9 @@ describe('ccs-browser MCP server', () => {
}, },
], ],
{ {
serverPath: installedServerPath, serverPath: bootstrapServerPath,
childEnv: { childEnv: {
NODE_PATH: nodeModulesDir, NODE_PATH: join(process.cwd(), 'node_modules'),
}, },
} }
); );