Let the tests run again

Every suite had stopped executing. ts-jest peers on `typescript >=4.3 <7`
and this repo builds against 7, so all twenty files died inside ConfigSet
with "Cannot read properties of undefined (reading 'fileExists')" — twenty
failures, zero tests, and nothing to read them by because the push gate
deliberately runs typecheck and build rather than jest.

swc transforms instead: it strips types rather than asking the compiler
to, so it has no TypeScript version to disagree with, and `tsc --noEmit`
already answers the question ts-jest was answering twice.

Running as real ESM then exposed what the CommonJS fallback had hidden.
jest.mock does not hoist there, so a static import binds the real module
and the suite fails on `mockReturnValue is not a function` — an absent
mock reported as a broken one. The search, autogui and ui suites now
register through unstable_mockModule and import after it. One of them had
never mocked fs at all: it cast fs.promises to a mocked type and called
mockImplementation on the real module.

384 tests, twenty suites, all passing.
This commit is contained in:
2026-09-01 09:42:54 -07:00
parent 0e21522f6a
commit c724ae360a
11 changed files with 294 additions and 40 deletions
+10 -11
View File
@@ -1,23 +1,22 @@
/** @type {import('jest').Config} */
export default {
preset: 'ts-jest/presets/default-esm',
extensionsToTreatAsEsm: ['.ts'],
testEnvironment: 'node',
testMatch: [
'**/test/**/*.test.ts',
'**/test/**/*.spec.ts'
],
// swc, not ts-jest: ts-jest peers on `typescript >=4.3 <7` and this repo builds
// against 7, so every suite died in ConfigSet before a test ran — 20 files, 0
// tests, reported as twenty separate failures. swc strips types instead of
// asking the compiler to, so it has no TypeScript version to disagree with.
//
// Nothing is lost by not typechecking here: `tsc --noEmit` is a gate of its own
// and runs on every push, so a type error fails the build rather than one suite.
transform: {
'^.+\\.ts$': ['ts-jest', {
useESM: true,
tsconfig: {
module: 'ESNext',
moduleResolution: 'node',
// Tests live outside ./src; widen rootDir so ts-jest can typecheck
// suites that import test/setup.ts (the production build uses esbuild
// and its own entrypoints, so this override is test-only).
rootDir: '.'
}
'^.+\\.ts$': ['@swc/jest', {
jsc: { parser: { syntax: 'typescript' }, target: 'es2022' },
module: { type: 'es6' },
}]
},
moduleNameMapper: {
+3 -1
View File
@@ -23,7 +23,7 @@
"build:cli": "esbuild src/cli.ts --bundle --platform=node --target=node18 --format=esm --outfile=dist/cli.js --external:@modelcontextprotocol/sdk --external:glob --external:commander --external:zod --external:zod-to-json-schema --external:mime-types --external:@lancedb/lancedb --external:@xenova/transformers --external:jsautogui --external:playwright --external:playwright-core --external:path --external:fs --external:fs/promises --external:os --external:url && chmod +x dist/cli.js",
"dev": "tsc --watch",
"smoke": "node scripts/smoke-mcp.mjs",
"test": "jest",
"test": "NODE_OPTIONS=--experimental-vm-modules jest",
"lint": "echo 'No linter configured'",
"prepublishOnly": "pnpm build"
},
@@ -56,6 +56,8 @@
},
"devDependencies": {
"@jest/globals": "^30.3.0",
"@swc/core": "^1.16.1",
"@swc/jest": "^0.2.39",
"@types/glob": "^9.0.0",
"@types/jest": "^30.0.0",
"@types/minimatch": "^6.0.0",
+223
View File
@@ -36,6 +36,12 @@ importers:
'@jest/globals':
specifier: ^30.3.0
version: 30.3.0([email protected])
'@swc/core':
specifier: ^1.16.1
version: 1.16.1(@swc/[email protected])
'@swc/jest':
specifier: ^0.2.39
version: 0.2.39(@swc/[email protected](@swc/[email protected]))
'@types/glob':
specifier: ^9.0.0
version: 9.0.0
@@ -448,6 +454,10 @@ packages:
node-notifier:
optional: true
'@jest/[email protected]':
resolution: {integrity: sha512-JgbpptteT/bylXg6bV7urexrJqwx3FBRK8LrWwAE16hQI1PRFmbT7Ur2ZyeeVtq577A22PLzIIeezuLFUIcAgA==}
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
'@jest/[email protected]':
resolution: {integrity: sha512-cG51MVnLq1ecVUaQ3fr6YuuAOitHK1S4WUJHnsPFE/quQr33ADUx1FfrTCpMCRxvy0Yr9BThKpDjSlcTi91tMA==}
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
@@ -480,6 +490,10 @@ packages:
resolution: {integrity: sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==}
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
'@jest/[email protected]':
resolution: {integrity: sha512-HdNQYSdRTEBNrginaqzQtTjG0HRMfrra/z6Ok7uL3S87vSlarIVohEsJsSj5edu3MiHoHjAkvPROz5ZjoKai+w==}
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
'@jest/[email protected]':
resolution: {integrity: sha512-a09z89S+PkQnL055bVj8+pe2Caed2PBOaczHcXCykW5ngxX9EWx/1uAwncxc/HiU0oZqfwseMjyhxgRjS49qPw==}
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
@@ -493,6 +507,10 @@ packages:
resolution: {integrity: sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==}
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
'@jest/[email protected]':
resolution: {integrity: sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==}
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
'@jest/[email protected]':
resolution: {integrity: sha512-ORbRN9sf5PP82v3FXNSwmO1OTDR2vzR2YTaR+E3VkSBZ8zadQE6IqYdYEeFH1NIkeB2HIGdF02dapb6K0Mj05g==}
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
@@ -517,6 +535,10 @@ packages:
resolution: {integrity: sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==}
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
'@jest/[email protected]':
resolution: {integrity: sha512-s1N+79S4Yp9ZgklCauZXi+YPJdCdtStNYQT32stuD6EeQaIBGHoUfyj2P0YWy8RmuQfaJboO+ulxEvEheR/POQ==}
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
'@jridgewell/[email protected]':
resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
@@ -655,9 +677,108 @@ packages:
'@sinonjs/[email protected]':
resolution: {integrity: sha512-cO5W33JgAPbOh07tvZjUOJ7oWhtaqGHiZw+11DPbyqh2kHTBc3eF/CjJDeQ4205RLQsX6rxCuYOroFQwl7JDRw==}
'@swc/[email protected]':
resolution: {integrity: sha512-zlJblJ8ncErD43lKdxjbUaUskJQf+LxiPXYcWXD8/8ZMV+7uuAT+CwjciLXpyZBd5Pq/S726bMpeeAwSeL1hhg==}
engines: {node: '>=10'}
cpu: [arm64]
os: [darwin]
'@swc/[email protected]':
resolution: {integrity: sha512-IN0BmPWb0YAh/17mmlWB/HDBtTw2MfuW4hulf/tQAgTQBRH17l+z499bNJLK6LizSjqs0P7V+jU38Zj+vJC1DA==}
engines: {node: '>=10'}
cpu: [x64]
os: [darwin]
'@swc/[email protected]':
resolution: {integrity: sha512-EYgrx2YOCQ2Twz2S793kqNjPkpvYVUPzzR95bIb7by+VQcyaai4lZZ2iz/tZvcFVKSNcN3/JTKwx+aBn2ZL52A==}
engines: {node: '>=10'}
cpu: [arm]
os: [linux]
'@swc/[email protected]':
resolution: {integrity: sha512-moyKm0YZlHdHohzm1YwgAyesqnE853rO0REMfJLFAova51wF9BNi+3ZW2PeS7Vqvn6HeJuepLpAHbBdZctxpHA==}
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@swc/[email protected]':
resolution: {integrity: sha512-kKGBO9wdapiSzuf5ZzZ2fYtlu1BNSYtIIUxvH1ir/gcelTOREEHGDCLTDFx/2Knf878nU11A40z7LxwasEFxqA==}
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@swc/[email protected]':
resolution: {integrity: sha512-nZ6qahtLxC3PM54cWOQZHxt4lTCF/3J4LIoWWzz6v7A+rLs8Dx54anYQf7mH3eIi8KlNpgKci/ie8ZSqFN8O7A==}
engines: {node: '>=10'}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@swc/[email protected]':
resolution: {integrity: sha512-4ji5PNzhYq193Z4/4xUaSoNJza6iCkDJSzhetrbB6KOYxsr+kxtQr8ePWhMJUiMt6JUWtXaZ1PYT8FhtED+nGA==}
engines: {node: '>=10'}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@swc/[email protected]':
resolution: {integrity: sha512-VJQxqrisHV+B394IgrOu8YsIIXZgffnf5tO+yc9Z/hoUpuZEvuQTjWwlnpZdpyD+0nx6LTD1/3k646JYm43yJA==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@swc/[email protected]':
resolution: {integrity: sha512-r9oV1mwxxsIGcLV1IQ/tw76MW3doatKze1QFWuC+a7QqJUkhY/bKTSVk6NpKKUGm2LDsE33Va8VqSClfA7vSiQ==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
libc: [musl]
'@swc/[email protected]':
resolution: {integrity: sha512-6huNRessoBLxWEqBm5zJXyCQ27TO7anvkdiuQ5MDO4CJni0nOXEqKtV9RllQ2TdyENKKsUMXVnIfW2hIXx/R5Q==}
engines: {node: '>=10'}
cpu: [arm64]
os: [win32]
'@swc/[email protected]':
resolution: {integrity: sha512-OVKJFUzphrGmsh+BGtcZDesx0YryV7/Yvy5XGgTqnrZfjnyfcr5uaqYQugCckdIlupc5Vs3XtDjRAj12z4ZPlw==}
engines: {node: '>=10'}
cpu: [ia32]
os: [win32]
'@swc/[email protected]':
resolution: {integrity: sha512-Bt+VIhWYCGk4urklnkkteLUOeLv1VxigwTCeB/xC6rBZxY6IIKdDwCJf6on3E3SUGsIqmQS6QqtuJQc1VxF4Aw==}
engines: {node: '>=10'}
cpu: [x64]
os: [win32]
'@swc/[email protected]':
resolution: {integrity: sha512-nUaeu91O5QZKrQdaDCHd402ogUIoNOOjpkZNq0UomWK0G6gDaGmLhvddF1/3BXf5O8aLyo6ZPY/aMDWvaJQ/hg==}
engines: {node: '>=10'}
peerDependencies:
'@swc/helpers': '>=0.5.17'
peerDependenciesMeta:
'@swc/helpers':
optional: true
'@swc/[email protected]':
resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
'@swc/[email protected]':
resolution: {integrity: sha512-QamiFeIK3txNjgUTNppE6MiG3p7TdninpZu0E0PbqVh1a9FNLT2FRhisaa4NcaX52XVhA5l7Pk58Ft7Sqi/2sA==}
'@swc/[email protected]':
resolution: {integrity: sha512-eyokjOwYd0Q8RnMHri+8/FS1HIrIUKK/sRrFp8c1dThUOfNeCWbLmBP1P5VsKdvmkd25JaH+OKYwEYiAYg9YAA==}
engines: {npm: '>= 7.0.0'}
peerDependencies:
'@swc/core': '*'
'@swc/[email protected]':
resolution: {integrity: sha512-V6Mnml8v09QALx6K0elJ7o9K/MkVDtW3t6L+7Ou/JcWtb3xwId2AH4FeOceySd2JaO87IMw4+6vSZxLm34LPbw==}
'@tybys/[email protected]':
resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
@@ -1763,6 +1884,10 @@ packages:
resolution: {integrity: sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==}
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
[email protected]:
resolution: {integrity: sha512-Mg0WK7A6xRHLSA1udJ8y9f3lM0uUhFTBnLKzwPmqB9AylvpleJ6BLemR8K9dK27DY+cesDryoA7yLZCAHsPG1A==}
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
[email protected]:
resolution: {integrity: sha512-9ev8s3YN6Hsyz9LV75XUwkCVFlwPbaFn6Wp75qnI0wzAINYWY8Fb3+6y59Rwd3QaS3kKXffHXsZMziMavfz/nw==}
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
@@ -1845,6 +1970,9 @@ packages:
engines: {node: '>=6'}
hasBin: true
[email protected]:
resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==}
[email protected]:
resolution: {integrity: sha512-JYmGFZemdd5LXShi+eo7yz5YWTsEYIwvLNIfGqLyaWzEZM96AHV8Q2mjvGMlSvnmSYTNCQU73nRhdihK2WsjMw==}
@@ -2966,6 +3094,10 @@ snapshots:
- supports-color
- ts-node
'@jest/[email protected]':
dependencies:
'@jest/types': 30.5.0
'@jest/[email protected]': {}
'@jest/[email protected]':
@@ -3011,6 +3143,11 @@ snapshots:
'@types/node': 25.5.0
jest-regex-util: 30.0.1
'@jest/[email protected]':
dependencies:
'@types/node': 25.5.0
jest-regex-util: 30.5.0
'@jest/[email protected]([email protected])':
dependencies:
'@bcoe/v8-coverage': 0.2.3
@@ -3043,6 +3180,10 @@ snapshots:
dependencies:
'@sinclair/typebox': 0.34.48
'@jest/[email protected]':
dependencies:
'@sinclair/typebox': 0.34.48
'@jest/[email protected]':
dependencies:
'@jest/types': 30.3.0
@@ -3099,6 +3240,16 @@ snapshots:
'@types/yargs': 17.0.35
chalk: 4.1.2
'@jest/[email protected]':
dependencies:
'@jest/pattern': 30.5.0
'@jest/schemas': 30.5.0
'@types/istanbul-lib-coverage': 2.0.6
'@types/istanbul-reports': 3.0.4
'@types/node': 25.5.0
'@types/yargs': 17.0.35
chalk: 4.1.2
'@jridgewell/[email protected]':
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
@@ -3246,11 +3397,79 @@ snapshots:
dependencies:
'@sinonjs/commons': 3.0.1
'@swc/[email protected]':
optional: true
'@swc/[email protected]':
optional: true
'@swc/[email protected]':
optional: true
'@swc/[email protected]':
optional: true
'@swc/[email protected]':
optional: true
'@swc/[email protected]':
optional: true
'@swc/[email protected]':
optional: true
'@swc/[email protected]':
optional: true
'@swc/[email protected]':
optional: true
'@swc/[email protected]':
optional: true
'@swc/[email protected]':
optional: true
'@swc/[email protected]':
optional: true
'@swc/[email protected](@swc/[email protected])':
dependencies:
'@swc/counter': 0.1.3
'@swc/types': 0.1.28
optionalDependencies:
'@swc/core-darwin-arm64': 1.16.1
'@swc/core-darwin-x64': 1.16.1
'@swc/core-linux-arm-gnueabihf': 1.16.1
'@swc/core-linux-arm64-gnu': 1.16.1
'@swc/core-linux-arm64-musl': 1.16.1
'@swc/core-linux-ppc64-gnu': 1.16.1
'@swc/core-linux-s390x-gnu': 1.16.1
'@swc/core-linux-x64-gnu': 1.16.1
'@swc/core-linux-x64-musl': 1.16.1
'@swc/core-win32-arm64-msvc': 1.16.1
'@swc/core-win32-ia32-msvc': 1.16.1
'@swc/core-win32-x64-msvc': 1.16.1
'@swc/helpers': 0.5.19
'@swc/[email protected]': {}
'@swc/[email protected]':
dependencies:
tslib: 2.8.1
optional: true
'@swc/[email protected](@swc/[email protected](@swc/[email protected]))':
dependencies:
'@jest/create-cache-key-function': 30.5.0
'@swc/core': 1.16.1(@swc/[email protected])
'@swc/counter': 0.1.3
jsonc-parser: 3.3.1
'@swc/[email protected]':
dependencies:
'@swc/counter': 0.1.3
'@tybys/[email protected]':
dependencies:
tslib: 2.8.1
@@ -4440,6 +4659,8 @@ snapshots:
[email protected]: {}
[email protected]: {}
[email protected]([email protected]):
dependencies:
jest-regex-util: 30.0.1
@@ -4618,6 +4839,8 @@ snapshots:
[email protected]: {}
[email protected]: {}
[email protected]:
optional: true
+2
View File
@@ -1,5 +1,6 @@
# Install scripts run only for packages listed here.
allowBuilds:
'@swc/core': true
esbuild: true
jsautogui: true
protobufjs: true
@@ -11,3 +12,4 @@ allowBuilds:
minimumReleaseAgeExclude:
- '@hanzo/*'
- '@hanzogui/*'
+13 -7
View File
@@ -1,20 +1,26 @@
import { describe, test, expect, beforeEach, afterEach, jest } from '@jest/globals';
import { autoguiTools } from '../../src/autogui/tools/autogui-tools.js';
// Mock external dependencies
jest.mock('../../src/autogui/factory.js', () => ({
// unstable_mockModule + await import, NOT jest.mock + a static import: ESM has no
// hoisting for jest.mock to rely on, so a static import binds the REAL module and
// the suite dies on `mockReturnValue is not a function` — an absent mock reported
// as a broken one.
jest.unstable_mockModule('../../src/autogui/factory.js', () => ({
createAutoGUI: jest.fn(),
getAvailableAutoGUIImplementations: jest.fn(),
getAutoGUIImplementationStatus: jest.fn(),
}));
const factory = await import('../../src/autogui/factory.js');
const { autoguiTools } = await import('../../src/autogui/tools/autogui-tools.js');
describe('AutoGUI Tools', () => {
beforeEach(() => {
jest.clearAllMocks();
// Mock factory functions
const { createAutoGUI, getAvailableAutoGUIImplementations, getAutoGUIImplementationStatus } = require('../../src/autogui/factory.js');
const { createAutoGUI, getAvailableAutoGUIImplementations, getAutoGUIImplementationStatus } = factory;
getAvailableAutoGUIImplementations.mockResolvedValue(['base', 'mock']);
getAutoGUIImplementationStatus.mockResolvedValue({
@@ -204,7 +210,7 @@ describe('AutoGUI Tools', () => {
describe('Error handling', () => {
test('should handle AutoGUI initialization failures', async () => {
const { createAutoGUI } = require('../../src/autogui/factory.js');
const { createAutoGUI } = factory;
createAutoGUI.mockRejectedValue(new Error('AutoGUI not available'));
const statusTool = autoguiTools.find(tool => tool.name === 'autogui_status');
@@ -220,7 +226,7 @@ describe('AutoGUI Tools', () => {
});
test('should handle missing implementations', async () => {
const { getAvailableAutoGUIImplementations } = require('../../src/autogui/factory.js');
const { getAvailableAutoGUIImplementations } = factory;
getAvailableAutoGUIImplementations.mockResolvedValue([]);
const statusTool = autoguiTools.find(tool => tool.name === 'autogui_status');
@@ -260,7 +266,7 @@ describe('AutoGUI Tools', () => {
describe('Factory integration', () => {
test('should interact with factory functions', () => {
const { createAutoGUI, getAvailableAutoGUIImplementations, getAutoGUIImplementationStatus } = require('../../src/autogui/factory.js');
const { createAutoGUI, getAvailableAutoGUIImplementations, getAutoGUIImplementationStatus } = factory;
// Verify mocks are set up (indicates proper factory integration)
expect(createAutoGUI).toBeDefined();
@@ -281,7 +287,7 @@ describe('AutoGUI Tools', () => {
expect(result1.content[0].text).toBe(result2.content[0].text);
const { getAvailableAutoGUIImplementations } = require('../../src/autogui/factory.js');
const { getAvailableAutoGUIImplementations } = factory;
expect(getAvailableAutoGUIImplementations).toHaveBeenCalled();
}
});
+1
View File
@@ -8,6 +8,7 @@
* into the default list would undo that without failing anything else.
*/
import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';
import { cloudTools, operations } from '../src/tools/cloud.js';
import { getConfiguredTools } from '../src/tools/index.js';
import catalog from '../src/tools/catalog.json';
+12 -9
View File
@@ -4,27 +4,23 @@
*/
import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';
import { executeSearch, executeFetch, executeSearchTool } from '../../src/search/index.js';
import { SearchEngine } from '../../src/search/search-engine.js';
import { getSecureTunnel } from '../../src/search/secure-tunnel.js';
import { getUrlHelper, configureUrlHelper } from '../../src/search/url-helper.js';
import * as fs from 'fs';
import * as path from 'path';
import { spawn } from 'child_process';
// Mock glob to avoid ESM/CJS resolution issues
jest.mock('glob', () => ({
// unstable_mockModule + await import, NOT jest.mock + a static import: ESM has no
// hoisting for jest.mock to rely on, so a static import binds the REAL module.
jest.unstable_mockModule('glob', () => ({
glob: jest.fn<() => Promise<string[]>>().mockResolvedValue([]),
Glob: jest.fn(),
}));
// Mock child_process for ngrok
jest.mock('child_process', () => ({
jest.unstable_mockModule('child_process', () => ({
spawn: jest.fn()
}));
// Mock file system for testing
jest.mock('fs', () => ({
jest.unstable_mockModule('fs', () => ({
promises: {
readFile: jest.fn(),
stat: jest.fn(),
@@ -33,6 +29,13 @@ jest.mock('fs', () => ({
}
}));
const fs = await import('fs');
const { executeSearch, executeFetch, executeSearchTool } = await import('../../src/search/index.js');
const { SearchEngine } = await import('../../src/search/search-engine.js');
const { getSecureTunnel } = await import('../../src/search/secure-tunnel.js');
const { getUrlHelper, configureUrlHelper } = await import('../../src/search/url-helper.js');
const { spawn } = await import('child_process');
describe('MCP Search Integration', () => {
let originalEnv: NodeJS.ProcessEnv;
let testDir: string;
+9 -3
View File
@@ -5,14 +5,20 @@
import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';
import { IncomingMessage, ServerResponse } from 'http';
import { Socket } from 'net';
import { SecureTunnel, SecureTunnelConfig, getSecureTunnel, shouldEnableNgrok } from '../../src/search/secure-tunnel.js';
import { spawn } from 'child_process';
// Mock child_process spawn
jest.mock('child_process', () => ({
// unstable_mockModule + await import, NOT jest.mock + a static import: ESM has no
// hoisting for jest.mock to rely on, so a static import binds the REAL module and
// the suite dies on `mockReturnValue is not a function` — an absent mock reported
// as a broken one.
jest.unstable_mockModule('child_process', () => ({
spawn: jest.fn()
}));
const { SecureTunnel, SecureTunnelConfig, getSecureTunnel, shouldEnableNgrok } = await import('../../src/search/secure-tunnel.js');
const { spawn } = await import('child_process');
describe('SecureTunnel', () => {
let tunnel: SecureTunnel;
let originalEnv: NodeJS.ProcessEnv;
+10 -4
View File
@@ -3,17 +3,23 @@
*/
import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';
import { UrlHelper, UrlConfig, getUrlHelper, configureUrlHelper, resetUrlHelper } from '../../src/search/url-helper.js';
import * as secureTunnel from '../../src/search/secure-tunnel.js';
import path from 'path';
// Mock secure tunnel
jest.mock('../../src/search/secure-tunnel.js', () => ({
// unstable_mockModule + await import, NOT jest.mock + a static import. Under ESM
// there is no hoisting for jest.mock to rely on, so the static import binds the
// REAL module and the suite fails on the first `mockReturnValue is not a
// function` — which reads as a broken mock rather than an absent one.
jest.unstable_mockModule('../../src/search/secure-tunnel.js', () => ({
getSecureTunnel: jest.fn(() => ({
getTunnelUrl: jest.fn(() => null)
}))
}));
const secureTunnel = await import('../../src/search/secure-tunnel.js');
const { UrlHelper, getUrlHelper, configureUrlHelper, resetUrlHelper } =
await import('../../src/search/url-helper.js');
type UrlConfig = import('../../src/search/url-helper.js').UrlConfig;
describe('UrlHelper', () => {
let urlHelper: UrlHelper;
let originalEnv: NodeJS.ProcessEnv;
+1
View File
@@ -1,3 +1,4 @@
import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';
import { fetchTool } from '../../src/tools/unified/fetch.js';
/**
+10 -5
View File
@@ -1,13 +1,18 @@
import { describe, test, expect, beforeEach, afterEach, jest } from '@jest/globals';
import { uiTools } from '../../src/ui/ui-tools.js';
// Mock external dependencies
jest.mock('../../src/ui/registry-api.js', () => ({
// unstable_mockModule + await import, NOT jest.mock + a static import: ESM has no
// hoisting for jest.mock to rely on, so a static import binds the REAL module and
// the suite dies on `mockReturnValue is not a function`.
jest.unstable_mockModule('../../src/ui/registry-api.js', () => ({
fetchRegistry: jest.fn(),
getRegistryItem: jest.fn(),
getRegistryItemUrl: jest.fn(),
}));
const registry = await import('../../src/ui/registry-api.js');
const { uiTools } = await import('../../src/ui/ui-tools.js');
describe('UI Tools', () => {
beforeEach(() => {
@@ -52,7 +57,7 @@ describe('UI Tools', () => {
const tool = uiTools.find(t => t.name.includes('list') || t.name.includes('registry'));
if (tool) {
const { fetchRegistry } = require('../../src/ui/registry-api.js');
const { fetchRegistry } = registry;
fetchRegistry.mockRejectedValue(new Error('Network error'));
const result = await tool.handler({});
@@ -115,7 +120,7 @@ describe('UI Tools', () => {
describe('Error handling', () => {
test('should handle network failures', async () => {
const { fetchRegistry } = require('../../src/ui/registry-api.js');
const { fetchRegistry } = registry;
fetchRegistry.mockRejectedValue(new Error('Network timeout'));
// Try to execute tools that might depend on network
@@ -137,7 +142,7 @@ describe('UI Tools', () => {
});
test('should handle malformed registry data', async () => {
const { fetchRegistry } = require('../../src/ui/registry-api.js');
const { fetchRegistry } = registry;
fetchRegistry.mockResolvedValue([{ invalid: 'data' }]);
const registryTools = uiTools.filter(tool =>