mirror of
https://github.com/hanzoai/mcp.git
synced 2026-10-15 12:30:12 +00:00
* feat(fetch): x402 Payment Required handling + first unified-tool tests
Adds HTTP 402 (x402) parsing to the unified fetch tool so AI agents can
surface payment quotes from x402-gated endpoints and replay with X-PAYMENT.
- request / fetch / head detect status === 402 and return a structured
payment_required envelope (parsed accepts[], x402-version, www-authenticate)
- new optional 'payment' input: pre-encoded base64 string sent verbatim, or
a JSON object that is base64-encoded into X-PAYMENT before retry
- adds the first Jest test file for the HIP-0300 unified surface
(test/tools/unified/fetch.test.ts) covering 200, 402, payment object, and
payment string paths against an in-process http.createServer fixture
Also fixes test/setup.ts so the existing test suite actually runs:
- imports 'jest' from @jest/globals (jest is not a global under ESM)
- ESM-safe __dirname via fileURLToPath(import.meta.url)
Net effect: 169 previously-unrunnable tests can now start; 7/7 new fetch
tests pass; remaining failures are pre-existing test bugs unrelated to this
PR (CommonJS require() in ESM mode, missing mocks, etc.).
No new dependencies, no breaking changes to fetch's existing semantics.
* test: anchor setup.ts on process.cwd() to fix Node 18/20 ESM CI
`test/setup.ts` used `fileURLToPath(import.meta.url)` to derive its own
directory, but `ts-jest`'s handling of `import.meta` inside
`setupFilesAfterEnv` is fragile across Node versions — every test suite
failed on Node 18.x and 20.x with TS1343 ("\'import.meta\' meta-property
is only allowed when \'--module\' is es2020/...\"), even though the
root tsconfig and ts-jest's transform tsconfig both set
`module: ESNext`.
Tests always run from the project root, so `path.join(process.cwd(),
'test', ...)` is a stable anchor that doesn't depend on how the setup
file is loaded.
Local: 15/16 suites pass after the fix (2 unrelated shell.test.ts timing
flakes remain).
* fix(ci): suppress TS2307 on optional jsautogui dynamic import
The Node 20 CI job was failing two test suites — test/config/
configuration.test.ts and test/integration/mcp-server.test.ts — with:
src/autogui/adapters/jsautogui.ts:37:37 - error TS2307: Cannot find
module 'jsautogui' or its corresponding type declarations.
while the actual jest run itself reported 326/326 tests passing in the
suites it could load. Root cause: jsautogui is in optionalDependencies
and ships native bindings that npm may skip on some platforms. When
the package (and its bundled .d.ts) isn't present, ts-jest's transform
pass fails the whole suite at type-check time even though the runtime
code path is already guarded by try/catch + AutoGUINotAvailableError.
Adds a localised `// @ts-ignore` on the single dynamic-import line,
with a comment explaining why. The runtime behaviour is unchanged —
if jsautogui isn't installed, the catch arm still throws
AutoGUINotAvailableError as before. Confirmed locally that the
previously-failing test/config/configuration.test.ts now passes both
with jsautogui present and with node_modules/jsautogui removed
(simulating the Ubuntu CI install).
No code logic changed; package.json and package-lock.json untouched.
---------
Co-authored-by: Abhishek Krishna <[email protected]>
100 lines
3.0 KiB
TypeScript
100 lines
3.0 KiB
TypeScript
/**
|
|
* Jest test setup file
|
|
*/
|
|
|
|
import * as fs from 'fs/promises';
|
|
import * as path from 'path';
|
|
import { beforeAll, afterAll, beforeEach, afterEach, jest } from '@jest/globals';
|
|
|
|
// Anchor on the project root rather than this file's directory so we don't
|
|
// need `import.meta.url` here — ts-jest's handling of `import.meta` in
|
|
// `setupFilesAfterEnv` differs across Node versions and breaks Node 18/20 CI.
|
|
// Tests always run from the project root, so `process.cwd()` is stable.
|
|
const TEST_ROOT = path.join(process.cwd(), 'test');
|
|
|
|
// Global test timeout
|
|
jest.setTimeout(10000);
|
|
|
|
// Test fixtures directory
|
|
export const TEST_FIXTURES_DIR = path.join(TEST_ROOT, 'fixtures');
|
|
|
|
// Each suite gets a unique temp directory to prevent parallel suite interference.
|
|
// setupFilesAfterEnv creates a fresh module instance per suite, so this value is
|
|
// unique per suite even though it's module-scoped.
|
|
const suiteId = `${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
export const TEST_TEMP_DIR = path.join(TEST_ROOT, 'temp', suiteId);
|
|
|
|
// Create test directories
|
|
beforeAll(async () => {
|
|
try {
|
|
await fs.mkdir(TEST_FIXTURES_DIR, { recursive: true });
|
|
await fs.mkdir(TEST_TEMP_DIR, { recursive: true });
|
|
} catch (error) {
|
|
// Directories might already exist
|
|
}
|
|
});
|
|
|
|
// Clean up this suite's unique temp subdirectory
|
|
afterAll(async () => {
|
|
try {
|
|
await fs.rm(TEST_TEMP_DIR, { recursive: true, force: true });
|
|
} catch (error) {
|
|
// Directory might not exist
|
|
}
|
|
});
|
|
|
|
// Mock console methods to reduce test noise
|
|
const originalConsoleError = console.error;
|
|
const originalConsoleWarn = console.warn;
|
|
|
|
beforeEach(() => {
|
|
// Mock console.error and console.warn unless we're specifically testing them
|
|
console.error = jest.fn();
|
|
console.warn = jest.fn();
|
|
});
|
|
|
|
afterEach(() => {
|
|
// Restore console methods
|
|
console.error = originalConsoleError;
|
|
console.warn = originalConsoleWarn;
|
|
|
|
// Clear all mocks
|
|
jest.clearAllMocks();
|
|
});
|
|
|
|
// Helper to create test files
|
|
export async function createTestFile(relativePath: string, content: string): Promise<string> {
|
|
const fullPath = path.join(TEST_TEMP_DIR, relativePath);
|
|
const dir = path.dirname(fullPath);
|
|
await fs.mkdir(dir, { recursive: true });
|
|
await fs.writeFile(fullPath, content, 'utf8');
|
|
return fullPath;
|
|
}
|
|
|
|
// Helper to read test file
|
|
export async function readTestFile(relativePath: string): Promise<string> {
|
|
const fullPath = path.join(TEST_TEMP_DIR, relativePath);
|
|
return fs.readFile(fullPath, 'utf8');
|
|
}
|
|
|
|
// Helper to check if test file exists
|
|
export async function testFileExists(relativePath: string): Promise<boolean> {
|
|
try {
|
|
const fullPath = path.join(TEST_TEMP_DIR, relativePath);
|
|
await fs.access(fullPath);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Helper to cleanup test files
|
|
export async function cleanupTestFile(relativePath: string): Promise<void> {
|
|
try {
|
|
const fullPath = path.join(TEST_TEMP_DIR, relativePath);
|
|
await fs.rm(fullPath, { force: true });
|
|
} catch {
|
|
// File might not exist
|
|
}
|
|
}
|