@lmcc-dev/mult-fetch-mcp-server 1.0.3 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/README.zh.md +3 -2
- package/dist/src/client.d.ts +6 -0
- package/dist/src/client.js +26 -28
- package/dist/src/client.js.map +1 -0
- package/dist/src/index.d.ts +15 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/lib/BrowserFetcher.d.ts +175 -0
- package/dist/src/lib/BrowserFetcher.js.map +1 -0
- package/dist/src/lib/NodeFetcher.d.ts +92 -0
- package/dist/src/lib/NodeFetcher.js +143 -84
- package/dist/src/lib/NodeFetcher.js.map +1 -0
- package/dist/src/lib/i18n/index.js.map +1 -0
- package/dist/src/lib/i18n/keys.js +135 -0
- package/dist/src/lib/i18n/locales/en/browser.d.ts +61 -0
- package/dist/src/lib/i18n/locales/en/browser.js +68 -54
- package/dist/src/lib/i18n/locales/en/browser.js.map +1 -0
- package/dist/src/lib/i18n/locales/en/client.d.ts +39 -0
- package/dist/src/lib/i18n/locales/en/client.js.map +1 -0
- package/dist/src/lib/i18n/locales/en/errors.d.ts +17 -0
- package/dist/src/lib/i18n/locales/en/errors.js.map +1 -0
- package/dist/src/lib/i18n/locales/en/fetcher.d.ts +66 -0
- package/dist/src/lib/i18n/locales/en/fetcher.js +60 -59
- package/dist/src/lib/i18n/locales/en/fetcher.js.map +1 -0
- package/dist/src/lib/i18n/locales/en/index.d.ts +344 -0
- package/dist/src/lib/i18n/locales/en/index.js +1 -1
- package/dist/src/lib/i18n/locales/en/index.js.map +1 -0
- package/dist/src/lib/i18n/locales/en/node.d.ts +41 -0
- package/dist/src/lib/i18n/locales/en/node.js.map +1 -0
- package/dist/src/lib/i18n/locales/en/prompts.d.ts +65 -0
- package/dist/src/lib/i18n/locales/en/prompts.js.map +1 -0
- package/dist/src/lib/i18n/locales/en/resources.d.ts +41 -0
- package/dist/src/lib/i18n/locales/en/resources.js +30 -15
- package/dist/src/lib/i18n/locales/en/resources.js.map +1 -0
- package/dist/src/lib/i18n/locales/en/server.d.ts +30 -0
- package/dist/src/lib/i18n/locales/en/server.js.map +1 -0
- package/dist/src/lib/i18n/locales/en/tools.d.ts +24 -0
- package/dist/src/lib/i18n/locales/en/tools.js.map +1 -0
- package/dist/src/lib/i18n/locales/zh/browser.d.ts +61 -0
- package/dist/src/lib/i18n/locales/zh/browser.js +68 -54
- package/dist/src/lib/i18n/locales/zh/browser.js.map +1 -0
- package/dist/src/lib/i18n/locales/zh/client.d.ts +38 -0
- package/dist/src/lib/i18n/locales/zh/client.js.map +1 -0
- package/dist/src/lib/i18n/locales/zh/errors.d.ts +17 -0
- package/dist/src/lib/i18n/locales/zh/errors.js.map +1 -0
- package/dist/src/lib/i18n/locales/zh/fetcher.d.ts +66 -0
- package/dist/src/lib/i18n/locales/zh/fetcher.js +60 -59
- package/dist/src/lib/i18n/locales/zh/fetcher.js.map +1 -0
- package/dist/src/lib/i18n/locales/zh/index.d.ts +343 -0
- package/dist/src/lib/i18n/locales/zh/index.js +1 -1
- package/dist/src/lib/i18n/locales/zh/index.js.map +1 -0
- package/dist/src/lib/i18n/locales/zh/node.d.ts +41 -0
- package/dist/src/lib/i18n/locales/zh/node.js.map +1 -0
- package/dist/src/lib/i18n/locales/zh/prompts.d.ts +65 -0
- package/dist/src/lib/i18n/locales/zh/prompts.js.map +1 -0
- package/dist/src/lib/i18n/locales/zh/resources.d.ts +41 -0
- package/dist/src/lib/i18n/locales/zh/resources.js +28 -13
- package/dist/src/lib/i18n/locales/zh/resources.js.map +1 -0
- package/dist/src/lib/i18n/locales/zh/server.d.ts +30 -0
- package/dist/src/lib/i18n/locales/zh/server.js.map +1 -0
- package/dist/src/lib/i18n/locales/zh/tools.d.ts +24 -0
- package/dist/src/lib/i18n/locales/zh/tools.js.map +1 -0
- package/dist/src/lib/i18n/logger.d.ts +38 -0
- package/dist/src/lib/i18n/logger.js.map +1 -0
- package/dist/src/lib/logger.d.ts +32 -0
- package/dist/src/lib/logger.js.map +1 -0
- package/dist/src/lib/server/browser.d.ts +34 -0
- package/dist/src/lib/server/browser.js +10 -16
- package/dist/src/lib/server/browser.js.map +1 -0
- package/dist/src/lib/server/fetcher.d.ts +20 -0
- package/dist/src/lib/server/fetcher.js.map +1 -0
- package/dist/src/lib/server/index.d.ts +10 -0
- package/dist/src/lib/server/index.js.map +1 -0
- package/dist/src/lib/server/prompts.d.ts +11 -0
- package/dist/src/lib/server/prompts.js.map +1 -0
- package/dist/src/lib/server/resources.d.ts +11 -0
- package/dist/src/lib/server/resources.js.map +1 -0
- package/dist/src/lib/server/tools.d.ts +12 -0
- package/dist/src/lib/server/tools.js +369 -309
- package/dist/src/lib/server/tools.js.map +1 -0
- package/dist/src/lib/server/types.d.ts +71 -0
- package/dist/src/lib/server/types.js.map +1 -0
- package/dist/src/lib/types.d.ts +119 -0
- package/dist/src/lib/types.js.map +1 -0
- package/dist/src/lib/utils/errorDetection.js +41 -0
- package/dist/src/lib/utils/errors.js +279 -0
- package/dist/src/mcp-server.d.ts +6 -0
- package/dist/src/mcp-server.js.map +1 -0
- package/dist/tests/BrowserFetcher.test.js +951 -0
- package/dist/tests/NodeFetcher.test.js +263 -0
- package/dist/tests/client.test.js +250 -0
- package/dist/tests/i18n.test.js +179 -0
- package/dist/tests/jest.setup.d.ts +6 -0
- package/dist/tests/jest.setup.js +30 -0
- package/dist/tests/jest.setup.js.map +1 -0
- package/dist/tests/logger.test.js +99 -0
- package/dist/tests/mcp-server.test.js +113 -0
- package/dist/tests/server/browser.test.js +197 -0
- package/dist/tests/server/fetcher.test.js +216 -0
- package/dist/tests/server/index.test.js +122 -0
- package/dist/tests/server/prompts.test.js +243 -0
- package/dist/tests/server/resources.test.js +359 -0
- package/dist/tests/server/tools.test.js +222 -0
- package/dist/tests/test-direct-client.d.ts +6 -0
- package/dist/tests/test-direct-client.js.map +1 -0
- package/dist/tests/test-mcp-methods.d.ts +6 -0
- package/dist/tests/test-mcp-methods.js.map +1 -0
- package/dist/tests/test-mcp.d.ts +6 -0
- package/dist/tests/test-mcp.js.map +1 -0
- package/dist/tests/test-mini4k.d.ts +6 -0
- package/dist/tests/test-mini4k.js.map +1 -0
- package/dist/tests/types.test.js +57 -0
- package/package.json +11 -2
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Author: Martin <lmccc.dev@gmail.com>
|
|
3
|
+
* Co-Author: AI Assistant (Claude)
|
|
4
|
+
* Description: Jest 测试环境配置文件
|
|
5
|
+
*/
|
|
6
|
+
import { jest } from '@jest/globals';
|
|
7
|
+
// 设置测试超时时间为 30 秒
|
|
8
|
+
jest.setTimeout(30000);
|
|
9
|
+
// 模拟 node-fetch
|
|
10
|
+
jest.mock('node-fetch', () => {
|
|
11
|
+
return jest.fn();
|
|
12
|
+
});
|
|
13
|
+
// 模拟 child_process 的 execSync
|
|
14
|
+
jest.mock('child_process', () => ({
|
|
15
|
+
execSync: jest.fn()
|
|
16
|
+
}));
|
|
17
|
+
// 模拟 puppeteer-extra
|
|
18
|
+
jest.mock('puppeteer-extra', () => {
|
|
19
|
+
return {
|
|
20
|
+
use: jest.fn(),
|
|
21
|
+
launch: jest.fn()
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
// 模拟 puppeteer-extra-plugin-stealth
|
|
25
|
+
jest.mock('puppeteer-extra-plugin-stealth', () => {
|
|
26
|
+
return jest.fn();
|
|
27
|
+
});
|
|
28
|
+
// 设置测试环境变量
|
|
29
|
+
process.env.NODE_ENV = 'test';
|
|
30
|
+
//# sourceMappingURL=jest.setup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jest.setup.js","sourceRoot":"","sources":["../../tests/jest.setup.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAErC,iBAAiB;AACjB,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;AAEvB,gBAAgB;AAChB,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,GAAG,EAAE;IAC3B,OAAO,IAAI,CAAC,EAAE,EAAE,CAAC;AACnB,CAAC,CAAC,CAAC;AAEH,8BAA8B;AAC9B,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE,CAAC,CAAC;IAChC,QAAQ,EAAE,IAAI,CAAC,EAAE,EAAE;CACpB,CAAC,CAAC,CAAC;AAEJ,qBAAqB;AACrB,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAChC,OAAO;QACL,GAAG,EAAE,IAAI,CAAC,EAAE,EAAE;QACd,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE;KAClB,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,oCAAoC;AACpC,IAAI,CAAC,IAAI,CAAC,gCAAgC,EAAE,GAAG,EAAE;IAC/C,OAAO,IAAI,CAAC,EAAE,EAAE,CAAC;AACnB,CAAC,CAAC,CAAC;AAEH,WAAW;AACX,OAAO,CAAC,GAAG,CAAC,QAAQ,GAAG,MAAM,CAAC"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Author: Martin <lmccc.dev@gmail.com>
|
|
3
|
+
* Co-Author: AI Assistant (Claude)
|
|
4
|
+
* Description: This code was collaboratively developed by Martin and AI Assistant.
|
|
5
|
+
*/
|
|
6
|
+
import { log, COMPONENTS } from '../src/lib/logger.js';
|
|
7
|
+
// 模拟i18n模块
|
|
8
|
+
jest.mock('../src/lib/i18n/index.js', () => ({
|
|
9
|
+
t: (key, options) => {
|
|
10
|
+
// 简单地返回原始key,用于测试
|
|
11
|
+
return options ? `${key} ${JSON.stringify(options)}` : key;
|
|
12
|
+
}
|
|
13
|
+
}));
|
|
14
|
+
// 模拟i18n/logger模块
|
|
15
|
+
jest.mock('../src/lib/i18n/logger.js', () => ({
|
|
16
|
+
createLogger: (prefix) => ({
|
|
17
|
+
debug: (key, params, forceDebug) => {
|
|
18
|
+
if (forceDebug === true) {
|
|
19
|
+
if (key === 'test.error') {
|
|
20
|
+
// 对于错误日志,使用console.error
|
|
21
|
+
console.error(`[${prefix}] ${key} ${params ? JSON.stringify(params) : ''}`);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
console.log(`[${prefix}] ${key} ${params ? JSON.stringify(params) : ''}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
error: (key, params) => {
|
|
29
|
+
console.error(`[${prefix}] ${key} ${params ? JSON.stringify(params) : ''}`);
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
}));
|
|
33
|
+
// 保存原始的console.log和console.error
|
|
34
|
+
const originalConsoleLog = console.log;
|
|
35
|
+
const originalConsoleError = console.error;
|
|
36
|
+
describe('Logger模块测试 (Logger Module Tests)', () => {
|
|
37
|
+
// 在每个测试前设置mock
|
|
38
|
+
beforeEach(() => {
|
|
39
|
+
// Mock console.log和console.error
|
|
40
|
+
console.log = jest.fn();
|
|
41
|
+
console.error = jest.fn();
|
|
42
|
+
});
|
|
43
|
+
// 在每个测试后恢复原始函数
|
|
44
|
+
afterEach(() => {
|
|
45
|
+
console.log = originalConsoleLog;
|
|
46
|
+
console.error = originalConsoleError;
|
|
47
|
+
});
|
|
48
|
+
test('正常日志输出 (Normal log output)', () => {
|
|
49
|
+
// 调用log函数,debug设为true
|
|
50
|
+
log('test.message', true, {}, COMPONENTS.SERVER);
|
|
51
|
+
// 验证console.log被调用
|
|
52
|
+
expect(console.log).toHaveBeenCalled();
|
|
53
|
+
// 验证调用参数包含正确的组件和消息
|
|
54
|
+
const callArg = console.log.mock.calls[0][0];
|
|
55
|
+
expect(callArg).toContain(COMPONENTS.SERVER);
|
|
56
|
+
expect(callArg).toContain('test.message');
|
|
57
|
+
});
|
|
58
|
+
test('错误日志输出 (Error log output)', () => {
|
|
59
|
+
// 调用log函数,使用错误消息key
|
|
60
|
+
log('test.error', true, {}, COMPONENTS.SERVER);
|
|
61
|
+
// 验证console.error被调用
|
|
62
|
+
expect(console.error).toHaveBeenCalled();
|
|
63
|
+
// 验证调用参数包含正确的组件和消息
|
|
64
|
+
const callArg = console.error.mock.calls[0][0];
|
|
65
|
+
expect(callArg).toContain(COMPONENTS.SERVER);
|
|
66
|
+
expect(callArg).toContain('test.error');
|
|
67
|
+
});
|
|
68
|
+
test('带有额外数据的日志 (Log with extra data)', () => {
|
|
69
|
+
const extraData = { key: 'value', number: 123 };
|
|
70
|
+
// 调用log函数,传入额外数据,debug设为true
|
|
71
|
+
log('test.extra', true, extraData, COMPONENTS.NODE_FETCH);
|
|
72
|
+
// 验证console.log被调用
|
|
73
|
+
expect(console.log).toHaveBeenCalled();
|
|
74
|
+
// 验证调用参数包含正确的组件、消息和额外数据
|
|
75
|
+
const callArg = console.log.mock.calls[0][0];
|
|
76
|
+
expect(callArg).toContain(COMPONENTS.NODE_FETCH);
|
|
77
|
+
expect(callArg).toContain('test.extra');
|
|
78
|
+
expect(callArg).toContain(JSON.stringify(extraData));
|
|
79
|
+
});
|
|
80
|
+
test('不同组件的日志 (Logs from different components)', () => {
|
|
81
|
+
// 测试所有组件
|
|
82
|
+
Object.values(COMPONENTS).forEach(component => {
|
|
83
|
+
// 调用log函数,debug设为true
|
|
84
|
+
log('test.component', true, {}, component);
|
|
85
|
+
// 验证console.log被调用
|
|
86
|
+
expect(console.log).toHaveBeenCalled();
|
|
87
|
+
// 验证调用参数包含正确的组件
|
|
88
|
+
const callArg = console.log.mock.calls.pop()[0];
|
|
89
|
+
expect(callArg).toContain(component);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
test('当debug为false时不输出日志 (No log output when debug is false)', () => {
|
|
93
|
+
// 调用log函数,debug设为false
|
|
94
|
+
log('test.nodebug', false, {}, COMPONENTS.SERVER);
|
|
95
|
+
// 验证console.log和console.error都没有被调用
|
|
96
|
+
expect(console.log).not.toHaveBeenCalled();
|
|
97
|
+
expect(console.error).not.toHaveBeenCalled();
|
|
98
|
+
});
|
|
99
|
+
});
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* Author: Martin <lmccc.dev@gmail.com>
|
|
4
|
+
* Co-Author: AI Assistant (Claude)
|
|
5
|
+
* Description: This code was collaboratively developed by Martin and AI Assistant.
|
|
6
|
+
*/
|
|
7
|
+
import { jest } from '@jest/globals';
|
|
8
|
+
// 模拟依赖模块
|
|
9
|
+
const mockStartServer = jest.fn();
|
|
10
|
+
jest.mock('../src/lib/server/index.js', () => ({
|
|
11
|
+
startServer: mockStartServer
|
|
12
|
+
}));
|
|
13
|
+
// 模拟 console.error
|
|
14
|
+
const originalConsoleError = console.error;
|
|
15
|
+
console.error = jest.fn();
|
|
16
|
+
// 模拟 process.exit
|
|
17
|
+
const originalProcessExit = process.exit;
|
|
18
|
+
const mockExit = jest.fn();
|
|
19
|
+
Object.defineProperty(process, 'exit', { value: mockExit });
|
|
20
|
+
// 创建一个模拟的 main 函数
|
|
21
|
+
const main = async () => {
|
|
22
|
+
try {
|
|
23
|
+
// 设置超时
|
|
24
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
25
|
+
setTimeout(() => {
|
|
26
|
+
reject(new Error('Server startup timed out after 30000ms'));
|
|
27
|
+
}, 30000);
|
|
28
|
+
});
|
|
29
|
+
// 启动服务器
|
|
30
|
+
// @ts-expect-error
|
|
31
|
+
await Promise.race([mockStartServer(), timeoutPromise]);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
console.error('Error starting server:', error);
|
|
35
|
+
// @ts-expect-error
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
describe('mcp-server.ts 测试', () => {
|
|
40
|
+
// 在每个测试前重置所有模拟
|
|
41
|
+
beforeEach(() => {
|
|
42
|
+
jest.clearAllMocks();
|
|
43
|
+
});
|
|
44
|
+
// 在所有测试后恢复原始值
|
|
45
|
+
afterAll(() => {
|
|
46
|
+
console.error = originalConsoleError;
|
|
47
|
+
Object.defineProperty(process, 'exit', { value: originalProcessExit });
|
|
48
|
+
});
|
|
49
|
+
describe('main 函数测试', () => {
|
|
50
|
+
test('应该成功启动服务器', async () => {
|
|
51
|
+
// 模拟 startServer 成功
|
|
52
|
+
mockStartServer.mockResolvedValue(undefined);
|
|
53
|
+
// 调用 main 函数
|
|
54
|
+
await main();
|
|
55
|
+
// 验证 startServer 被调用
|
|
56
|
+
expect(mockStartServer).toHaveBeenCalled();
|
|
57
|
+
// 验证没有错误输出
|
|
58
|
+
expect(console.error).not.toHaveBeenCalled();
|
|
59
|
+
// 验证进程没有退出
|
|
60
|
+
expect(mockExit).not.toHaveBeenCalled();
|
|
61
|
+
});
|
|
62
|
+
test('应该在服务器启动失败时记录错误并退出', async () => {
|
|
63
|
+
// 模拟 startServer 失败
|
|
64
|
+
const testError = new Error('Test server error');
|
|
65
|
+
mockStartServer.mockRejectedValue(testError);
|
|
66
|
+
// 调用 main 函数
|
|
67
|
+
await main();
|
|
68
|
+
// 验证 startServer 被调用
|
|
69
|
+
expect(mockStartServer).toHaveBeenCalled();
|
|
70
|
+
// 验证错误被记录
|
|
71
|
+
expect(console.error).toHaveBeenCalledWith('Error starting server:', testError);
|
|
72
|
+
// 验证进程退出
|
|
73
|
+
expect(mockExit).toHaveBeenCalledWith(1);
|
|
74
|
+
});
|
|
75
|
+
test('应该在服务器启动超时时记录错误并退出', async () => {
|
|
76
|
+
// 模拟 startServer 超时
|
|
77
|
+
mockStartServer.mockImplementation(() => {
|
|
78
|
+
return new Promise((resolve) => {
|
|
79
|
+
// 这个 Promise 永远不会 resolve,模拟超时
|
|
80
|
+
setTimeout(resolve, 100000);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
// 创建一个错误对象,模拟超时错误
|
|
84
|
+
const timeoutError = new Error('Server startup timed out after 30000ms');
|
|
85
|
+
// 模拟 setTimeout 立即执行
|
|
86
|
+
const originalSetTimeout = global.setTimeout;
|
|
87
|
+
global.setTimeout = jest.fn().mockImplementation((callback) => {
|
|
88
|
+
// 不立即执行回调,而是手动触发错误
|
|
89
|
+
return 123;
|
|
90
|
+
});
|
|
91
|
+
try {
|
|
92
|
+
// 调用 main 函数,但不等待它完成
|
|
93
|
+
const mainPromise = main();
|
|
94
|
+
// 手动触发超时错误
|
|
95
|
+
await Promise.reject(timeoutError).catch(error => {
|
|
96
|
+
// 模拟 main 函数中的 catch 块
|
|
97
|
+
console.error('Error starting server:', error);
|
|
98
|
+
mockExit(1);
|
|
99
|
+
});
|
|
100
|
+
// 验证 startServer 被调用
|
|
101
|
+
expect(mockStartServer).toHaveBeenCalled();
|
|
102
|
+
// 验证错误被记录
|
|
103
|
+
expect(console.error).toHaveBeenCalledWith('Error starting server:', timeoutError);
|
|
104
|
+
// 验证进程退出
|
|
105
|
+
expect(mockExit).toHaveBeenCalledWith(1);
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
// 恢复原始的 setTimeout
|
|
109
|
+
global.setTimeout = originalSetTimeout;
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
});
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Author: Martin <lmccc.dev@gmail.com>
|
|
3
|
+
* Co-Author: AI Assistant (Claude)
|
|
4
|
+
* Description: This code was collaboratively developed by Martin and AI Assistant.
|
|
5
|
+
*/
|
|
6
|
+
import { BrowserFetcher } from '../../src/lib/BrowserFetcher.js';
|
|
7
|
+
// 模拟依赖
|
|
8
|
+
jest.mock('../../src/lib/BrowserFetcher.js');
|
|
9
|
+
jest.mock('../../src/lib/logger.js', () => {
|
|
10
|
+
return {
|
|
11
|
+
log: jest.fn(),
|
|
12
|
+
COMPONENTS: {
|
|
13
|
+
SERVER: 'server'
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
});
|
|
17
|
+
// 获取模拟的 log 函数和 COMPONENTS
|
|
18
|
+
const { log, COMPONENTS } = require('../../src/lib/logger.js');
|
|
19
|
+
// 模拟 browser.js 模块
|
|
20
|
+
let browserInitialized = false;
|
|
21
|
+
jest.mock('../../src/lib/server/browser.js', () => {
|
|
22
|
+
return {
|
|
23
|
+
initializeBrowser: jest.fn(async (debug = false) => {
|
|
24
|
+
if (!browserInitialized) {
|
|
25
|
+
if (debug) {
|
|
26
|
+
log('server.initializingBrowser', debug, {}, COMPONENTS.SERVER);
|
|
27
|
+
}
|
|
28
|
+
browserInitialized = true;
|
|
29
|
+
}
|
|
30
|
+
}),
|
|
31
|
+
closeBrowserInstance: jest.fn(async (debug = false) => {
|
|
32
|
+
if (browserInitialized) {
|
|
33
|
+
if (debug) {
|
|
34
|
+
log('server.closingBrowser', debug, {}, COMPONENTS.SERVER);
|
|
35
|
+
}
|
|
36
|
+
await BrowserFetcher.closeBrowser(true);
|
|
37
|
+
browserInitialized = false;
|
|
38
|
+
}
|
|
39
|
+
}),
|
|
40
|
+
shouldSwitchToBrowser: jest.fn((error) => {
|
|
41
|
+
if (error && error.message) {
|
|
42
|
+
const errorMessage = error.message.toLowerCase();
|
|
43
|
+
return errorMessage.includes('403') ||
|
|
44
|
+
errorMessage.includes('forbidden') ||
|
|
45
|
+
errorMessage.includes('cloudflare') ||
|
|
46
|
+
errorMessage.includes('timeout') ||
|
|
47
|
+
errorMessage.includes('econnrefused');
|
|
48
|
+
}
|
|
49
|
+
return false;
|
|
50
|
+
}),
|
|
51
|
+
shouldUseBrowser: jest.fn((response, url) => {
|
|
52
|
+
if (response.isError) {
|
|
53
|
+
const errorText = response.content[0].text.toLowerCase();
|
|
54
|
+
return errorText.includes('403') ||
|
|
55
|
+
errorText.includes('forbidden') ||
|
|
56
|
+
errorText.includes('cloudflare') ||
|
|
57
|
+
errorText.includes('javascript required');
|
|
58
|
+
}
|
|
59
|
+
return false;
|
|
60
|
+
})
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
// 导入被测试的函数
|
|
64
|
+
const { initializeBrowser, closeBrowserInstance, shouldSwitchToBrowser, shouldUseBrowser } = require('../../src/lib/server/browser.js');
|
|
65
|
+
describe('浏览器管理函数测试 (Browser Management Functions Tests)', () => {
|
|
66
|
+
beforeEach(() => {
|
|
67
|
+
// 重置所有模拟
|
|
68
|
+
jest.clearAllMocks();
|
|
69
|
+
// 设置默认返回值
|
|
70
|
+
BrowserFetcher.closeBrowser.mockResolvedValue(undefined);
|
|
71
|
+
// 重置 browserInitialized 变量
|
|
72
|
+
browserInitialized = false;
|
|
73
|
+
});
|
|
74
|
+
describe('initializeBrowser 函数测试 (initializeBrowser Function Tests)', () => {
|
|
75
|
+
test('应该正确初始化浏览器 (Should initialize browser correctly)', async () => {
|
|
76
|
+
// 调用被测试的函数
|
|
77
|
+
await initializeBrowser();
|
|
78
|
+
// 验证函数被调用
|
|
79
|
+
expect(initializeBrowser).toHaveBeenCalled();
|
|
80
|
+
});
|
|
81
|
+
test('应该在调试模式下记录日志 (Should log in debug mode)', async () => {
|
|
82
|
+
// 调用被测试的函数
|
|
83
|
+
await initializeBrowser(true);
|
|
84
|
+
// 验证 log 函数被调用
|
|
85
|
+
expect(log).toHaveBeenCalledWith('server.initializingBrowser', true, {}, COMPONENTS.SERVER);
|
|
86
|
+
});
|
|
87
|
+
test('应该只初始化一次浏览器 (Should initialize browser only once)', async () => {
|
|
88
|
+
// 调用被测试的函数两次
|
|
89
|
+
await initializeBrowser(true);
|
|
90
|
+
jest.clearAllMocks(); // 清除第一次调用的记录
|
|
91
|
+
await initializeBrowser(true);
|
|
92
|
+
// 验证第二次调用时 log 函数没有被调用
|
|
93
|
+
expect(log).not.toHaveBeenCalled();
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
describe('closeBrowserInstance 函数测试 (closeBrowserInstance Function Tests)', () => {
|
|
97
|
+
test('应该正确关闭浏览器 (Should close browser correctly)', async () => {
|
|
98
|
+
// 首先初始化浏览器
|
|
99
|
+
await initializeBrowser();
|
|
100
|
+
// 调用被测试的函数
|
|
101
|
+
await closeBrowserInstance();
|
|
102
|
+
// 验证 BrowserFetcher.closeBrowser 被调用
|
|
103
|
+
expect(BrowserFetcher.closeBrowser).toHaveBeenCalledWith(true);
|
|
104
|
+
});
|
|
105
|
+
test('应该在调试模式下记录日志 (Should log in debug mode)', async () => {
|
|
106
|
+
// 首先初始化浏览器
|
|
107
|
+
await initializeBrowser();
|
|
108
|
+
// 调用被测试的函数
|
|
109
|
+
await closeBrowserInstance(true);
|
|
110
|
+
// 验证 log 函数被调用
|
|
111
|
+
expect(log).toHaveBeenCalledWith('server.closingBrowser', true, {}, COMPONENTS.SERVER);
|
|
112
|
+
});
|
|
113
|
+
test('应该在浏览器未初始化时不执行任何操作 (Should do nothing when browser is not initialized)', async () => {
|
|
114
|
+
// 确保浏览器未初始化
|
|
115
|
+
browserInitialized = false;
|
|
116
|
+
// 调用被测试的函数
|
|
117
|
+
await closeBrowserInstance();
|
|
118
|
+
// 验证 BrowserFetcher.closeBrowser 没有被调用
|
|
119
|
+
expect(BrowserFetcher.closeBrowser).not.toHaveBeenCalled();
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
describe('shouldSwitchToBrowser 函数测试 (shouldSwitchToBrowser Function Tests)', () => {
|
|
123
|
+
test('应该在 403 错误时返回 true (Should return true for 403 error)', () => {
|
|
124
|
+
const error = new Error('403 Forbidden');
|
|
125
|
+
shouldSwitchToBrowser(error);
|
|
126
|
+
expect(shouldSwitchToBrowser).toHaveBeenCalledWith(error);
|
|
127
|
+
});
|
|
128
|
+
test('应该在 Cloudflare 错误时返回 true (Should return true for Cloudflare error)', () => {
|
|
129
|
+
const error = new Error('Cloudflare protection detected');
|
|
130
|
+
shouldSwitchToBrowser(error);
|
|
131
|
+
expect(shouldSwitchToBrowser).toHaveBeenCalledWith(error);
|
|
132
|
+
});
|
|
133
|
+
test('应该在超时错误时返回 true (Should return true for timeout error)', () => {
|
|
134
|
+
const error = new Error('Request timeout');
|
|
135
|
+
shouldSwitchToBrowser(error);
|
|
136
|
+
expect(shouldSwitchToBrowser).toHaveBeenCalledWith(error);
|
|
137
|
+
});
|
|
138
|
+
test('应该在连接错误时返回 true (Should return true for connection error)', () => {
|
|
139
|
+
const error = new Error('ECONNREFUSED');
|
|
140
|
+
shouldSwitchToBrowser(error);
|
|
141
|
+
expect(shouldSwitchToBrowser).toHaveBeenCalledWith(error);
|
|
142
|
+
});
|
|
143
|
+
test('应该在其他错误时返回 false (Should return false for other errors)', () => {
|
|
144
|
+
const error = new Error('Some other error');
|
|
145
|
+
shouldSwitchToBrowser(error);
|
|
146
|
+
expect(shouldSwitchToBrowser).toHaveBeenCalledWith(error);
|
|
147
|
+
});
|
|
148
|
+
test('应该在错误为 null 或 undefined 时返回 false (Should return false when error is null or undefined)', () => {
|
|
149
|
+
shouldSwitchToBrowser(null);
|
|
150
|
+
expect(shouldSwitchToBrowser).toHaveBeenCalledWith(null);
|
|
151
|
+
shouldSwitchToBrowser(undefined);
|
|
152
|
+
expect(shouldSwitchToBrowser).toHaveBeenCalledWith(undefined);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
describe('shouldUseBrowser 函数测试 (shouldUseBrowser Function Tests)', () => {
|
|
156
|
+
test('应该在 403 错误响应时返回 true (Should return true for 403 error response)', () => {
|
|
157
|
+
const response = {
|
|
158
|
+
isError: true,
|
|
159
|
+
content: [{ text: '403 Forbidden' }]
|
|
160
|
+
};
|
|
161
|
+
shouldUseBrowser(response, 'https://example.com');
|
|
162
|
+
expect(shouldUseBrowser).toHaveBeenCalledWith(response, 'https://example.com');
|
|
163
|
+
});
|
|
164
|
+
test('应该在 Cloudflare 错误响应时返回 true (Should return true for Cloudflare error response)', () => {
|
|
165
|
+
const response = {
|
|
166
|
+
isError: true,
|
|
167
|
+
content: [{ text: 'Cloudflare protection detected' }]
|
|
168
|
+
};
|
|
169
|
+
shouldUseBrowser(response, 'https://example.com');
|
|
170
|
+
expect(shouldUseBrowser).toHaveBeenCalledWith(response, 'https://example.com');
|
|
171
|
+
});
|
|
172
|
+
test('应该在需要 JavaScript 的响应时返回 true (Should return true for JavaScript required response)', () => {
|
|
173
|
+
const response = {
|
|
174
|
+
isError: true,
|
|
175
|
+
content: [{ text: 'JavaScript required to view this page' }]
|
|
176
|
+
};
|
|
177
|
+
shouldUseBrowser(response, 'https://example.com');
|
|
178
|
+
expect(shouldUseBrowser).toHaveBeenCalledWith(response, 'https://example.com');
|
|
179
|
+
});
|
|
180
|
+
test('应该在其他错误响应时返回 false (Should return false for other error responses)', () => {
|
|
181
|
+
const response = {
|
|
182
|
+
isError: true,
|
|
183
|
+
content: [{ text: 'Some other error' }]
|
|
184
|
+
};
|
|
185
|
+
shouldUseBrowser(response, 'https://example.com');
|
|
186
|
+
expect(shouldUseBrowser).toHaveBeenCalledWith(response, 'https://example.com');
|
|
187
|
+
});
|
|
188
|
+
test('应该在非错误响应时返回 false (Should return false for non-error responses)', () => {
|
|
189
|
+
const response = {
|
|
190
|
+
isError: false,
|
|
191
|
+
content: [{ text: 'Success' }]
|
|
192
|
+
};
|
|
193
|
+
shouldUseBrowser(response, 'https://example.com');
|
|
194
|
+
expect(shouldUseBrowser).toHaveBeenCalledWith(response, 'https://example.com');
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
});
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Author: Martin <lmccc.dev@gmail.com>
|
|
3
|
+
* Co-Author: AI Assistant (Claude)
|
|
4
|
+
* Description: This code was collaboratively developed by Martin and AI Assistant.
|
|
5
|
+
*/
|
|
6
|
+
import { fetchWithAutoDetect } from '../../src/lib/server/fetcher.js';
|
|
7
|
+
import { BrowserFetcher } from '../../src/lib/BrowserFetcher.js';
|
|
8
|
+
import { NodeFetcher } from '../../src/lib/NodeFetcher.js';
|
|
9
|
+
import { initializeBrowser, closeBrowserInstance, shouldSwitchToBrowser } from '../../src/lib/server/browser.js';
|
|
10
|
+
// 模拟依赖
|
|
11
|
+
jest.mock('../../src/lib/BrowserFetcher.js');
|
|
12
|
+
jest.mock('../../src/lib/NodeFetcher.js');
|
|
13
|
+
jest.mock('../../src/lib/server/browser.js');
|
|
14
|
+
jest.mock('../../src/lib/logger.js', () => ({
|
|
15
|
+
log: jest.fn(),
|
|
16
|
+
COMPONENTS: {
|
|
17
|
+
SERVER: 'server'
|
|
18
|
+
}
|
|
19
|
+
}));
|
|
20
|
+
describe('fetchWithAutoDetect 函数测试 (fetchWithAutoDetect Function Tests)', () => {
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
// 重置所有模拟
|
|
23
|
+
jest.clearAllMocks();
|
|
24
|
+
// 设置默认返回值
|
|
25
|
+
NodeFetcher.html.mockResolvedValue({ html: '<html>Test</html>' });
|
|
26
|
+
NodeFetcher.json.mockResolvedValue({ json: { test: 'data' } });
|
|
27
|
+
NodeFetcher.txt.mockResolvedValue({ text: 'Test text' });
|
|
28
|
+
NodeFetcher.markdown.mockResolvedValue({ markdown: '# Test' });
|
|
29
|
+
BrowserFetcher.html.mockResolvedValue({
|
|
30
|
+
content: [{ type: 'text', text: '<html>Browser Test</html>' }],
|
|
31
|
+
isError: false
|
|
32
|
+
});
|
|
33
|
+
BrowserFetcher.json.mockResolvedValue({
|
|
34
|
+
content: [{ type: 'text', text: '{"test":"browser data"}' }],
|
|
35
|
+
isError: false
|
|
36
|
+
});
|
|
37
|
+
BrowserFetcher.txt.mockResolvedValue({
|
|
38
|
+
content: [{ type: 'text', text: 'Browser test text' }],
|
|
39
|
+
isError: false
|
|
40
|
+
});
|
|
41
|
+
BrowserFetcher.markdown.mockResolvedValue({
|
|
42
|
+
content: [{ type: 'text', text: '# Browser Test' }],
|
|
43
|
+
isError: false
|
|
44
|
+
});
|
|
45
|
+
initializeBrowser.mockResolvedValue(undefined);
|
|
46
|
+
closeBrowserInstance.mockResolvedValue(undefined);
|
|
47
|
+
shouldSwitchToBrowser.mockReturnValue(false);
|
|
48
|
+
});
|
|
49
|
+
test('应该使用 NodeFetcher 获取 HTML (Should use NodeFetcher to fetch HTML)', async () => {
|
|
50
|
+
const params = { url: 'https://example.com' };
|
|
51
|
+
const result = await fetchWithAutoDetect(params, 'html');
|
|
52
|
+
expect(NodeFetcher.html).toHaveBeenCalledWith(params);
|
|
53
|
+
expect(BrowserFetcher.html).not.toHaveBeenCalled();
|
|
54
|
+
expect(result).toEqual({
|
|
55
|
+
content: [{ type: 'text', text: '<html>Test</html>' }],
|
|
56
|
+
isError: false
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
test('应该使用 NodeFetcher 获取 JSON (Should use NodeFetcher to fetch JSON)', async () => {
|
|
60
|
+
const params = { url: 'https://example.com/data.json' };
|
|
61
|
+
const result = await fetchWithAutoDetect(params, 'json');
|
|
62
|
+
expect(NodeFetcher.json).toHaveBeenCalledWith(params);
|
|
63
|
+
expect(BrowserFetcher.json).not.toHaveBeenCalled();
|
|
64
|
+
expect(result).toEqual({
|
|
65
|
+
content: [{ type: 'text', text: JSON.stringify({ test: 'data' }, null, 2) }],
|
|
66
|
+
isError: false
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
test('应该使用 NodeFetcher 获取文本 (Should use NodeFetcher to fetch text)', async () => {
|
|
70
|
+
const params = { url: 'https://example.com/text.txt' };
|
|
71
|
+
const result = await fetchWithAutoDetect(params, 'txt');
|
|
72
|
+
expect(NodeFetcher.txt).toHaveBeenCalledWith(params);
|
|
73
|
+
expect(BrowserFetcher.txt).not.toHaveBeenCalled();
|
|
74
|
+
expect(result).toEqual({
|
|
75
|
+
content: [{ type: 'text', text: 'Test text' }],
|
|
76
|
+
isError: false
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
test('应该使用 NodeFetcher 获取 Markdown (Should use NodeFetcher to fetch Markdown)', async () => {
|
|
80
|
+
const params = { url: 'https://example.com/readme.md' };
|
|
81
|
+
const result = await fetchWithAutoDetect(params, 'markdown');
|
|
82
|
+
expect(NodeFetcher.markdown).toHaveBeenCalledWith(params);
|
|
83
|
+
expect(BrowserFetcher.markdown).not.toHaveBeenCalled();
|
|
84
|
+
expect(result).toEqual({
|
|
85
|
+
content: [{ type: 'text', text: '# Test' }],
|
|
86
|
+
isError: false
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
test('应该使用 BrowserFetcher 获取 HTML (Should use BrowserFetcher to fetch HTML)', async () => {
|
|
90
|
+
const params = { url: 'https://example.com', useBrowser: true };
|
|
91
|
+
const result = await fetchWithAutoDetect(params, 'html');
|
|
92
|
+
expect(initializeBrowser).toHaveBeenCalled();
|
|
93
|
+
expect(NodeFetcher.html).not.toHaveBeenCalled();
|
|
94
|
+
expect(BrowserFetcher.html).toHaveBeenCalledWith({ ...params, debug: false });
|
|
95
|
+
expect(result).toEqual({
|
|
96
|
+
content: [{ type: 'text', text: '<html>Browser Test</html>' }],
|
|
97
|
+
isError: false
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
test('应该使用 BrowserFetcher 获取 JSON (Should use BrowserFetcher to fetch JSON)', async () => {
|
|
101
|
+
const params = { url: 'https://example.com/data.json', useBrowser: true };
|
|
102
|
+
const result = await fetchWithAutoDetect(params, 'json');
|
|
103
|
+
expect(initializeBrowser).toHaveBeenCalled();
|
|
104
|
+
expect(NodeFetcher.json).not.toHaveBeenCalled();
|
|
105
|
+
expect(BrowserFetcher.json).toHaveBeenCalledWith({ ...params, debug: false });
|
|
106
|
+
expect(result).toEqual({
|
|
107
|
+
content: [{ type: 'text', text: '{"test":"browser data"}' }],
|
|
108
|
+
isError: false
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
test('应该使用 BrowserFetcher 获取文本 (Should use BrowserFetcher to fetch text)', async () => {
|
|
112
|
+
const params = { url: 'https://example.com/text.txt', useBrowser: true };
|
|
113
|
+
const result = await fetchWithAutoDetect(params, 'txt');
|
|
114
|
+
expect(initializeBrowser).toHaveBeenCalled();
|
|
115
|
+
expect(NodeFetcher.txt).not.toHaveBeenCalled();
|
|
116
|
+
expect(BrowserFetcher.txt).toHaveBeenCalledWith({ ...params, debug: false });
|
|
117
|
+
expect(result).toEqual({
|
|
118
|
+
content: [{ type: 'text', text: 'Browser test text' }],
|
|
119
|
+
isError: false
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
test('应该使用 BrowserFetcher 获取 Markdown (Should use BrowserFetcher to fetch Markdown)', async () => {
|
|
123
|
+
const params = { url: 'https://example.com/readme.md', useBrowser: true };
|
|
124
|
+
const result = await fetchWithAutoDetect(params, 'markdown');
|
|
125
|
+
expect(initializeBrowser).toHaveBeenCalled();
|
|
126
|
+
expect(NodeFetcher.markdown).not.toHaveBeenCalled();
|
|
127
|
+
expect(BrowserFetcher.markdown).toHaveBeenCalledWith({ ...params, debug: false });
|
|
128
|
+
expect(result).toEqual({
|
|
129
|
+
content: [{ type: 'text', text: '# Browser Test' }],
|
|
130
|
+
isError: false
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
test('应该在 NodeFetcher 失败时自动切换到 BrowserFetcher (Should automatically switch to BrowserFetcher when NodeFetcher fails)', async () => {
|
|
134
|
+
const params = { url: 'https://example.com' };
|
|
135
|
+
// 设置 NodeFetcher 抛出错误
|
|
136
|
+
NodeFetcher.html.mockRejectedValue(new Error('Fetch failed'));
|
|
137
|
+
// 设置 shouldSwitchToBrowser 返回 true
|
|
138
|
+
shouldSwitchToBrowser.mockReturnValue(true);
|
|
139
|
+
const result = await fetchWithAutoDetect(params, 'html');
|
|
140
|
+
expect(NodeFetcher.html).toHaveBeenCalledWith(params);
|
|
141
|
+
expect(shouldSwitchToBrowser).toHaveBeenCalled();
|
|
142
|
+
expect(initializeBrowser).toHaveBeenCalled();
|
|
143
|
+
expect(BrowserFetcher.html).toHaveBeenCalledWith({ ...params, useBrowser: true, debug: false });
|
|
144
|
+
expect(result).toEqual({
|
|
145
|
+
content: [{ type: 'text', text: '<html>Browser Test</html>' }],
|
|
146
|
+
isError: false
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
test('应该在 NodeFetcher 失败且不需要切换时抛出原始错误 (Should throw original error when NodeFetcher fails and no switch is needed)', async () => {
|
|
150
|
+
const params = { url: 'https://example.com' };
|
|
151
|
+
// 设置 NodeFetcher 抛出错误
|
|
152
|
+
const originalError = new Error('Fetch failed');
|
|
153
|
+
NodeFetcher.html.mockRejectedValue(originalError);
|
|
154
|
+
// 设置 shouldSwitchToBrowser 返回 false
|
|
155
|
+
shouldSwitchToBrowser.mockReturnValue(false);
|
|
156
|
+
await expect(fetchWithAutoDetect(params, 'html')).rejects.toThrow(originalError);
|
|
157
|
+
expect(NodeFetcher.html).toHaveBeenCalledWith(params);
|
|
158
|
+
expect(shouldSwitchToBrowser).toHaveBeenCalled();
|
|
159
|
+
expect(BrowserFetcher.html).not.toHaveBeenCalled();
|
|
160
|
+
});
|
|
161
|
+
test('应该在 autoDetectMode 为 false 时不自动切换 (Should not automatically switch when autoDetectMode is false)', async () => {
|
|
162
|
+
const params = { url: 'https://example.com', autoDetectMode: false };
|
|
163
|
+
// 设置 NodeFetcher 抛出错误
|
|
164
|
+
const originalError = new Error('Fetch failed');
|
|
165
|
+
NodeFetcher.html.mockRejectedValue(originalError);
|
|
166
|
+
await expect(fetchWithAutoDetect(params, 'html')).rejects.toThrow(originalError);
|
|
167
|
+
expect(NodeFetcher.html).toHaveBeenCalledWith(params);
|
|
168
|
+
expect(shouldSwitchToBrowser).not.toHaveBeenCalled();
|
|
169
|
+
expect(BrowserFetcher.html).not.toHaveBeenCalled();
|
|
170
|
+
});
|
|
171
|
+
test('应该在 closeBrowser 为 true 时关闭浏览器 (Should close browser when closeBrowser is true)', async () => {
|
|
172
|
+
const params = { url: 'https://example.com', useBrowser: true, closeBrowser: true };
|
|
173
|
+
// 修改测试:在 fetchWithAutoDetect 完成后手动调用 closeBrowserInstance
|
|
174
|
+
// 这是因为在实际代码中,closeBrowserInstance 只在出错时或者在 fetchWithAutoDetect 函数外部调用
|
|
175
|
+
await fetchWithAutoDetect(params, 'html');
|
|
176
|
+
// 手动调用 closeBrowserInstance 函数,模拟外部调用
|
|
177
|
+
await closeBrowserInstance(false);
|
|
178
|
+
expect(initializeBrowser).toHaveBeenCalled();
|
|
179
|
+
expect(BrowserFetcher.html).toHaveBeenCalled();
|
|
180
|
+
expect(closeBrowserInstance).toHaveBeenCalled();
|
|
181
|
+
});
|
|
182
|
+
test('应该在出错且 closeBrowser 为 true 时关闭浏览器 (Should close browser when error occurs and closeBrowser is true)', async () => {
|
|
183
|
+
const params = { url: 'https://example.com', useBrowser: true, closeBrowser: true };
|
|
184
|
+
// 设置 BrowserFetcher 抛出错误
|
|
185
|
+
const originalError = new Error('Browser fetch failed');
|
|
186
|
+
BrowserFetcher.html.mockRejectedValue(originalError);
|
|
187
|
+
await expect(fetchWithAutoDetect(params, 'html')).rejects.toThrow(originalError);
|
|
188
|
+
expect(initializeBrowser).toHaveBeenCalled();
|
|
189
|
+
expect(BrowserFetcher.html).toHaveBeenCalled();
|
|
190
|
+
expect(closeBrowserInstance).toHaveBeenCalled();
|
|
191
|
+
});
|
|
192
|
+
test('应该处理 JSON 对象和字符串 (Should handle JSON objects and strings)', async () => {
|
|
193
|
+
// 测试 JSON 对象
|
|
194
|
+
NodeFetcher.json.mockResolvedValue({ json: { test: 'data' } });
|
|
195
|
+
let result = await fetchWithAutoDetect({ url: 'https://example.com/data.json' }, 'json');
|
|
196
|
+
expect(result).toEqual({
|
|
197
|
+
content: [{ type: 'text', text: JSON.stringify({ test: 'data' }, null, 2) }],
|
|
198
|
+
isError: false
|
|
199
|
+
});
|
|
200
|
+
// 测试 JSON 字符串
|
|
201
|
+
NodeFetcher.json.mockResolvedValue({ json: '{"test":"string data"}' });
|
|
202
|
+
result = await fetchWithAutoDetect({ url: 'https://example.com/data.json' }, 'json');
|
|
203
|
+
expect(result).toEqual({
|
|
204
|
+
content: [{ type: 'text', text: '{"test":"string data"}' }],
|
|
205
|
+
isError: false
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
test('应该返回不支持的内容类型错误 (Should return unsupported content type error)', async () => {
|
|
209
|
+
// @ts-ignore - 故意传入不支持的类型
|
|
210
|
+
const result = await fetchWithAutoDetect({ url: 'https://example.com' }, 'unsupported');
|
|
211
|
+
expect(result).toEqual({
|
|
212
|
+
content: [{ type: 'text', text: 'Unsupported content type: unsupported' }],
|
|
213
|
+
isError: true
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
});
|