Files
qiming/qimingclaw/crates/qiming-mcp-stdio-proxy/tests/remote-streamable.test.ts

138 lines
4.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Test: Remote MCP Server with Streamable HTTP (auth headers)
*/
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { writeFileSync, rmSync, readdirSync } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';
import { buildRequestHeaders } from '../src/transport/headers.js';
import type { StreamableServerEntry } from '../src/types.js';
// Test constants
const TEST_CONFIG_DIR = join(tmpdir(), 'test-mcp-remote-headers');
const TEST_URL = 'https://mcp.coze.cn/v1/plugins/7407724292865130515';
// 注意: 这是临时测试 token不要固化到代码中
const TEST_AUTH_TOKEN = process.env.TEST_AUTH_TOKEN || 'Bearer cztei_xxx';
const TEST_HEADERS = {
Authorization: TEST_AUTH_TOKEN,
};
// Helper to create a test config file
function createTestConfig(serverName: string, headers?: Record<string, string>): string {
const configPath = join(TEST_CONFIG_DIR, `${serverName}.json`);
const config = {
mcpServers: {
[serverName]: {
url: TEST_URL,
transport: 'streamable-http',
headers: headers || TEST_HEADERS,
},
},
};
writeFileSync(configPath, JSON.stringify(config), 'utf-8');
return configPath;
}
// Helper to cleanup test files
function cleanupTestFiles() {
try {
const files = readdirSync(TEST_CONFIG_DIR);
for (const file of files) {
rmSync(file);
}
} catch {
// ignore
}
}
describe('buildRequestHeaders', () => {
beforeEach(() => {
cleanupTestFiles();
});
it('should return undefined when entry has no headers or authToken', () => {
const entry: StreamableServerEntry = {
url: TEST_URL,
transport: 'streamable-http',
};
const result = buildRequestHeaders(entry);
expect(result).toBeUndefined();
});
it('should return headers with Bearer prefix when entry has authToken', () => {
const rawToken = 'my-auth-token';
const entry: StreamableServerEntry = {
url: TEST_URL,
transport: 'streamable-http',
authToken: rawToken,
};
const result = buildRequestHeaders(entry);
expect(result).toEqual({
Authorization: `Bearer ${rawToken}`,
});
});
it('should return headers directly when entry has headers', () => {
const customHeaders = {
'X-Custom-Header': 'custom-value',
Authorization: 'CustomAuth custom-token',
};
const entry: StreamableServerEntry = {
url: TEST_URL,
transport: 'streamable-http',
headers: customHeaders,
};
const result = buildRequestHeaders(entry);
expect(result).toEqual(customHeaders);
});
it('should let authToken override headers.Authorization when both are provided', () => {
const customHeaders = {
Authorization: 'CustomAuth custom-token',
};
const entry: StreamableServerEntry = {
url: TEST_URL,
transport: 'streamable-http',
headers: customHeaders,
authToken: 'override-token',
};
const result = buildRequestHeaders(entry);
// authToken 会覆盖 headers 中的 Authorization
expect(result).toEqual({
Authorization: 'Bearer override-token',
});
});
it('should return undefined when entry is a stdio entry (no url)', () => {
const entry = {
command: 'npx',
args: ['-y', 'some-mcp-server'],
} as unknown as StreamableServerEntry;
const result = buildRequestHeaders(entry);
expect(result).toBeUndefined();
});
});
describe('detectProtocol with auth failure', () => {
// 这个测试需要真实的网络请求, 仅用于调试目的
// 注意: 不要在 CI 中启用, 因为 token 会过期
it.skip('should handle 401 auth failure gracefully', async () => {
const { detectProtocol } = await import('../src/detect.js');
const invalidToken = 'Bearer invalid-token-for-testing';
const result = await detectProtocol(TEST_URL, {
Authorization: invalidToken,
});
// 即使鉴权失败, 也应该返回一个协议类型(默认 sse
expect(['sse', 'stream']).toContain(result);
});
});