@lmcc-dev/mult-fetch-mcp-server 1.1.0 → 1.2.1
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 +8 -8
- package/README.zh.md +16 -8
- package/dist/i18n-test-report.json +8 -0
- package/dist/i18n-unused-keys-report.json +8 -0
- package/dist/src/client.js +70 -37
- package/dist/src/index.js +0 -0
- package/dist/src/lib/BrowserFetcher.js +12 -12
- package/dist/src/lib/NodeFetcher.js +22 -22
- package/dist/src/lib/fetch.js +40 -0
- package/dist/src/lib/fetchers/FetcherFactory.js +43 -0
- package/dist/src/lib/fetchers/browser/BrowserFetcher.js +381 -0
- package/dist/src/lib/fetchers/browser/BrowserInstance.js +180 -0
- package/dist/src/lib/fetchers/browser/PageOperations.js +168 -0
- package/dist/src/lib/fetchers/common/types.js +41 -0
- package/dist/src/lib/fetchers/common/utils.js +141 -0
- package/dist/src/lib/fetchers/index.js +62 -0
- package/dist/src/lib/fetchers/node/HttpClient.js +239 -0
- package/dist/src/lib/fetchers/node/NodeFetcher.js +180 -0
- package/dist/src/lib/i18n/index.js +105 -17
- package/dist/src/lib/i18n/keys/base.js +41 -0
- package/dist/src/lib/i18n/keys/browser.js +91 -0
- package/dist/src/lib/i18n/keys/client.js +68 -0
- package/dist/src/lib/i18n/keys/errors.js +49 -0
- package/dist/src/lib/i18n/keys/factory.js +46 -0
- package/dist/src/lib/i18n/keys/fetcher.js +87 -0
- package/dist/src/lib/i18n/keys/index.js +16 -0
- package/dist/src/lib/i18n/keys/node.js +69 -0
- package/dist/src/lib/i18n/keys/prompts.js +79 -0
- package/dist/src/lib/i18n/keys/resources.js +53 -0
- package/dist/src/lib/i18n/keys/server.js +67 -0
- package/dist/src/lib/i18n/keys/tools.js +39 -0
- package/dist/src/lib/i18n/keys.js +488 -119
- package/dist/src/lib/i18n/locales/en/browser.js +67 -57
- package/dist/src/lib/i18n/locales/en/client.js +57 -33
- package/dist/src/lib/i18n/locales/en/errors.js +36 -12
- package/dist/src/lib/i18n/locales/en/factory.js +38 -0
- package/dist/src/lib/i18n/locales/en/fetcher.js +8 -2
- package/dist/src/lib/i18n/locales/en/index.js +10 -8
- package/dist/src/lib/i18n/locales/en/node.js +57 -35
- package/dist/src/lib/i18n/locales/en/prompts.js +42 -65
- package/dist/src/lib/i18n/locales/en/resources.js +52 -55
- package/dist/src/lib/i18n/locales/en/server.js +49 -24
- package/dist/src/lib/i18n/locales/en/tools.js +27 -19
- package/dist/src/lib/i18n/locales/zh/browser.js +71 -62
- package/dist/src/lib/i18n/locales/zh/client.js +57 -32
- package/dist/src/lib/i18n/locales/zh/errors.js +36 -12
- package/dist/src/lib/i18n/locales/zh/factory.js +38 -0
- package/dist/src/lib/i18n/locales/zh/fetcher.js +36 -30
- package/dist/src/lib/i18n/locales/zh/index.js +10 -8
- package/dist/src/lib/i18n/locales/zh/node.js +61 -36
- package/dist/src/lib/i18n/locales/zh/prompts.js +35 -57
- package/dist/src/lib/i18n/locales/zh/resources.js +52 -55
- package/dist/src/lib/i18n/locales/zh/server.js +51 -25
- package/dist/src/lib/i18n/locales/zh/tools.js +27 -19
- package/dist/src/lib/i18n/logger.js +90 -19
- package/dist/src/lib/logger.js +135 -14
- package/dist/src/lib/server/browser.js +42 -10
- package/dist/src/lib/server/fetcher.js +87 -37
- package/dist/src/lib/server/index.js +2 -2
- package/dist/src/lib/server/prompts.js +1 -1
- package/dist/src/lib/server/resources.js +1 -1
- package/dist/src/lib/server/tools.js +46 -10
- package/dist/src/lib/utils/ErrorHandler.js +125 -0
- package/dist/src/mcp-server.js +0 -0
- package/dist/src/test-i18n.js +139 -0
- package/dist/tests/BrowserFetcher.test.js +0 -0
- package/dist/tests/NodeFetcher.test.js +0 -0
- package/dist/tests/client.test.js +42 -81
- package/dist/tests/fetch.test.js +70 -0
- package/dist/tests/fetchers/Fetcher.test.js +108 -0
- package/dist/tests/fetchers/browser/BrowserFetcher.test.js +195 -0
- package/dist/tests/fetchers/browser/BrowserInstance.test.js +163 -0
- package/dist/tests/fetchers/node/NodeFetcher.test.js +211 -0
- package/dist/tests/i18n-missing-keys.js +364 -0
- package/dist/tests/i18n-remove-unused-keys.js +236 -0
- package/dist/tests/i18n-test-report.json +2004 -0
- package/dist/tests/i18n-unused-keys.js +425 -0
- package/dist/tests/i18n.test.js +6 -7
- package/dist/tests/logger.test.js +14 -13
- package/dist/tests/mcp-server.test.js +7 -7
- package/dist/tests/server/browser.test.js +25 -21
- package/dist/tests/server/fetcher.test.js +58 -101
- package/dist/tests/server/index.test.js +30 -30
- package/dist/tests/server/prompts.test.js +36 -33
- package/dist/tests/server/resources.test.js +120 -84
- package/dist/tests/server/tools.test.js +7 -6
- package/dist/tests/setup.js +36 -0
- package/dist/tests/src/lib/i18n/index.js +108 -0
- package/dist/tests/src/lib/i18n/keys/base.js +47 -0
- package/dist/tests/src/lib/i18n/keys/browser.js +93 -0
- package/dist/tests/src/lib/i18n/keys/client.js +70 -0
- package/dist/tests/src/lib/i18n/keys/errors.js +34 -0
- package/dist/tests/src/lib/i18n/keys/fetcher.js +84 -0
- package/dist/tests/src/lib/i18n/keys/index.js +31 -0
- package/dist/tests/src/lib/i18n/keys/node.js +56 -0
- package/dist/tests/src/lib/i18n/keys/prompts.js +82 -0
- package/dist/tests/src/lib/i18n/keys/resources.js +50 -0
- package/dist/tests/src/lib/i18n/keys/server.js +64 -0
- package/dist/tests/src/lib/i18n/keys/tools.js +34 -0
- package/dist/tests/src/lib/i18n/locales/en/browser.js +88 -0
- package/dist/tests/src/lib/i18n/locales/en/client.js +66 -0
- package/dist/tests/src/lib/i18n/locales/en/errors.js +28 -0
- package/dist/tests/src/lib/i18n/locales/en/fetcher.js +71 -0
- package/dist/tests/src/lib/i18n/locales/en/index.js +29 -0
- package/dist/tests/src/lib/i18n/locales/en/node.js +51 -0
- package/dist/tests/src/lib/i18n/locales/en/prompts.js +52 -0
- package/dist/tests/src/lib/i18n/locales/en/resources.js +50 -0
- package/dist/tests/src/lib/i18n/locales/en/server.js +56 -0
- package/dist/tests/src/lib/i18n/locales/en/tools.js +28 -0
- package/dist/tests/src/lib/i18n/locales/zh/browser.js +87 -0
- package/dist/tests/src/lib/i18n/locales/zh/client.js +66 -0
- package/dist/tests/src/lib/i18n/locales/zh/errors.js +28 -0
- package/dist/tests/src/lib/i18n/locales/zh/fetcher.js +71 -0
- package/dist/tests/src/lib/i18n/locales/zh/index.js +29 -0
- package/dist/tests/src/lib/i18n/locales/zh/node.js +51 -0
- package/dist/tests/src/lib/i18n/locales/zh/prompts.js +53 -0
- package/dist/tests/src/lib/i18n/locales/zh/resources.js +50 -0
- package/dist/tests/src/lib/i18n/locales/zh/server.js +57 -0
- package/dist/tests/src/lib/i18n/locales/zh/tools.js +28 -0
- package/dist/tests/src/lib/i18n/logger.js +114 -0
- package/dist/tests/src/lib/logger.js +181 -0
- package/dist/tests/test-direct-client.js +0 -0
- package/dist/tests/test-i18n.js +530 -0
- package/dist/tests/test-mcp-methods.js +1 -1
- package/dist/tests/test-mcp.js +0 -0
- package/dist/tests/test-mini4k.js +0 -0
- package/dist/tests/tests/test-i18n.js +588 -0
- package/dist/tests/types.test.js +1 -1
- package/dist/tests/utils/ErrorHandler.test.js +144 -0
- package/dist/vitest.config.js +29 -0
- package/package.json +31 -27
- package/dist/src/cli-language.js +0 -78
- package/dist/src/client.d.ts +0 -6
- package/dist/src/client.js.map +0 -1
- package/dist/src/index.d.ts +0 -15
- package/dist/src/index.js.map +0 -1
- package/dist/src/lib/BrowserFetcher.d.ts +0 -175
- package/dist/src/lib/BrowserFetcher.js.map +0 -1
- package/dist/src/lib/NodeFetcher.d.ts +0 -92
- package/dist/src/lib/NodeFetcher.js.map +0 -1
- package/dist/src/lib/example.js +0 -93
- package/dist/src/lib/i18n/index.js.map +0 -1
- package/dist/src/lib/i18n/locales/en/browser.d.ts +0 -61
- package/dist/src/lib/i18n/locales/en/browser.js.map +0 -1
- package/dist/src/lib/i18n/locales/en/client.d.ts +0 -39
- package/dist/src/lib/i18n/locales/en/client.js.map +0 -1
- package/dist/src/lib/i18n/locales/en/errors.d.ts +0 -17
- package/dist/src/lib/i18n/locales/en/errors.js.map +0 -1
- package/dist/src/lib/i18n/locales/en/fetcher.d.ts +0 -66
- package/dist/src/lib/i18n/locales/en/fetcher.js.map +0 -1
- package/dist/src/lib/i18n/locales/en/index.d.ts +0 -344
- package/dist/src/lib/i18n/locales/en/index.js.map +0 -1
- package/dist/src/lib/i18n/locales/en/node.d.ts +0 -41
- package/dist/src/lib/i18n/locales/en/node.js.map +0 -1
- package/dist/src/lib/i18n/locales/en/prompts.d.ts +0 -65
- package/dist/src/lib/i18n/locales/en/prompts.js.map +0 -1
- package/dist/src/lib/i18n/locales/en/resources.d.ts +0 -41
- package/dist/src/lib/i18n/locales/en/resources.js.map +0 -1
- package/dist/src/lib/i18n/locales/en/server.d.ts +0 -30
- package/dist/src/lib/i18n/locales/en/server.js.map +0 -1
- package/dist/src/lib/i18n/locales/en/tools.d.ts +0 -24
- package/dist/src/lib/i18n/locales/en/tools.js.map +0 -1
- package/dist/src/lib/i18n/locales/zh/browser.d.ts +0 -61
- package/dist/src/lib/i18n/locales/zh/browser.js.map +0 -1
- package/dist/src/lib/i18n/locales/zh/client.d.ts +0 -38
- package/dist/src/lib/i18n/locales/zh/client.js.map +0 -1
- package/dist/src/lib/i18n/locales/zh/errors.d.ts +0 -17
- package/dist/src/lib/i18n/locales/zh/errors.js.map +0 -1
- package/dist/src/lib/i18n/locales/zh/fetcher.d.ts +0 -66
- package/dist/src/lib/i18n/locales/zh/fetcher.js.map +0 -1
- package/dist/src/lib/i18n/locales/zh/index.d.ts +0 -343
- package/dist/src/lib/i18n/locales/zh/index.js.map +0 -1
- package/dist/src/lib/i18n/locales/zh/node.d.ts +0 -41
- package/dist/src/lib/i18n/locales/zh/node.js.map +0 -1
- package/dist/src/lib/i18n/locales/zh/prompts.d.ts +0 -65
- package/dist/src/lib/i18n/locales/zh/prompts.js.map +0 -1
- package/dist/src/lib/i18n/locales/zh/resources.d.ts +0 -41
- package/dist/src/lib/i18n/locales/zh/resources.js.map +0 -1
- package/dist/src/lib/i18n/locales/zh/server.d.ts +0 -30
- package/dist/src/lib/i18n/locales/zh/server.js.map +0 -1
- package/dist/src/lib/i18n/locales/zh/tools.d.ts +0 -24
- package/dist/src/lib/i18n/locales/zh/tools.js.map +0 -1
- package/dist/src/lib/i18n/logger.d.ts +0 -38
- package/dist/src/lib/i18n/logger.js.map +0 -1
- package/dist/src/lib/logger.d.ts +0 -32
- package/dist/src/lib/logger.js.map +0 -1
- package/dist/src/lib/server/browser.d.ts +0 -34
- package/dist/src/lib/server/browser.js.map +0 -1
- package/dist/src/lib/server/fetcher.d.ts +0 -20
- package/dist/src/lib/server/fetcher.js.map +0 -1
- package/dist/src/lib/server/index.d.ts +0 -10
- package/dist/src/lib/server/index.js.map +0 -1
- package/dist/src/lib/server/logger.js +0 -23
- package/dist/src/lib/server/prompts.d.ts +0 -11
- package/dist/src/lib/server/prompts.js.map +0 -1
- package/dist/src/lib/server/resources.d.ts +0 -11
- package/dist/src/lib/server/resources.js.map +0 -1
- package/dist/src/lib/server/tools.d.ts +0 -12
- package/dist/src/lib/server/tools.js.map +0 -1
- package/dist/src/lib/server/types.d.ts +0 -71
- package/dist/src/lib/server/types.js.map +0 -1
- package/dist/src/lib/types.d.ts +0 -119
- package/dist/src/lib/types.js.map +0 -1
- package/dist/src/mcp-server.d.ts +0 -6
- package/dist/src/mcp-server.js.map +0 -1
- package/dist/tests/jest.setup.d.ts +0 -6
- package/dist/tests/jest.setup.js +0 -30
- package/dist/tests/jest.setup.js.map +0 -1
- package/dist/tests/test-direct-client.d.ts +0 -6
- package/dist/tests/test-direct-client.js.map +0 -1
- package/dist/tests/test-mcp-methods.d.ts +0 -6
- package/dist/tests/test-mcp-methods.js.map +0 -1
- package/dist/tests/test-mcp.d.ts +0 -6
- package/dist/tests/test-mcp.js.map +0 -1
- package/dist/tests/test-mini4k.d.ts +0 -6
- package/dist/tests/test-mini4k.js.map +0 -1
|
@@ -0,0 +1,163 @@
|
|
|
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 { describe, expect, test, beforeAll, afterAll, beforeEach, vi } from 'vitest';
|
|
7
|
+
import { BrowserInstance } from '../../../src/lib/fetchers/browser/BrowserInstance.js';
|
|
8
|
+
import { log, COMPONENTS } from '../../../src/lib/logger.js';
|
|
9
|
+
// 使用vi.hoisted创建模拟对象,确保它们在vi.mock之前被定义
|
|
10
|
+
const mockBrowser = vi.hoisted(() => ({
|
|
11
|
+
newPage: vi.fn(),
|
|
12
|
+
close: vi.fn(),
|
|
13
|
+
on: vi.fn()
|
|
14
|
+
}));
|
|
15
|
+
const mockLaunch = vi.hoisted(() => vi.fn().mockResolvedValue(mockBrowser));
|
|
16
|
+
// Mock Puppeteer
|
|
17
|
+
vi.mock('puppeteer', () => ({
|
|
18
|
+
__esModule: true,
|
|
19
|
+
default: {
|
|
20
|
+
launch: vi.fn()
|
|
21
|
+
}
|
|
22
|
+
}));
|
|
23
|
+
// Mock puppeteer-extra
|
|
24
|
+
vi.mock('puppeteer-extra', () => ({
|
|
25
|
+
__esModule: true,
|
|
26
|
+
default: {
|
|
27
|
+
use: vi.fn(),
|
|
28
|
+
launch: mockLaunch
|
|
29
|
+
}
|
|
30
|
+
}));
|
|
31
|
+
// Mock puppeteer-extra-plugin-stealth
|
|
32
|
+
vi.mock('puppeteer-extra-plugin-stealth', () => ({
|
|
33
|
+
__esModule: true,
|
|
34
|
+
default: vi.fn()
|
|
35
|
+
}));
|
|
36
|
+
// Mock logger
|
|
37
|
+
vi.mock('../../../src/lib/logger.js', () => ({
|
|
38
|
+
log: vi.fn(),
|
|
39
|
+
COMPONENTS: {
|
|
40
|
+
BROWSER_FETCH: 'BROWSER_FETCH'
|
|
41
|
+
}
|
|
42
|
+
}));
|
|
43
|
+
describe('BrowserInstance 类测试 (BrowserInstance Class Tests)', () => {
|
|
44
|
+
// 模拟 process.memoryUsage 返回值
|
|
45
|
+
const mockMemoryUsage = {
|
|
46
|
+
heapUsed: 100 * 1024 * 1024, // 100MB
|
|
47
|
+
heapTotal: 200 * 1024 * 1024, // 200MB
|
|
48
|
+
rss: 300 * 1024 * 1024, // 300MB
|
|
49
|
+
external: 50 * 1024 * 1024, // 50MB
|
|
50
|
+
arrayBuffers: 10 * 1024 * 1024 // 10MB
|
|
51
|
+
};
|
|
52
|
+
beforeAll(() => {
|
|
53
|
+
// 启用 Jest 的假计时器
|
|
54
|
+
vi.useFakeTimers();
|
|
55
|
+
});
|
|
56
|
+
afterAll(() => {
|
|
57
|
+
// 恢复真实计时器
|
|
58
|
+
vi.useRealTimers();
|
|
59
|
+
});
|
|
60
|
+
beforeEach(() => {
|
|
61
|
+
// 重置所有 mock
|
|
62
|
+
vi.clearAllMocks();
|
|
63
|
+
vi.clearAllTimers();
|
|
64
|
+
// 模拟 process.memoryUsage
|
|
65
|
+
vi.spyOn(process, 'memoryUsage').mockReturnValue(mockMemoryUsage);
|
|
66
|
+
// 重置 BrowserInstance 的静态属性
|
|
67
|
+
// @ts-ignore - 访问私有属性用于测试
|
|
68
|
+
BrowserInstance.browser = null;
|
|
69
|
+
// @ts-ignore
|
|
70
|
+
BrowserInstance.browserStarting = false;
|
|
71
|
+
// @ts-ignore
|
|
72
|
+
BrowserInstance.browserStartPromise = null;
|
|
73
|
+
// @ts-ignore
|
|
74
|
+
BrowserInstance.lastMemoryCheck = 0;
|
|
75
|
+
});
|
|
76
|
+
describe('内存管理测试 (Memory Management Tests)', () => {
|
|
77
|
+
test('应该正确检查内存使用情况 (Should check memory usage correctly)', () => {
|
|
78
|
+
// 调用方法
|
|
79
|
+
BrowserInstance.checkMemoryUsage(true);
|
|
80
|
+
// 验证 process.memoryUsage 被调用
|
|
81
|
+
expect(process.memoryUsage).toHaveBeenCalled();
|
|
82
|
+
// 验证日志被记录
|
|
83
|
+
expect(log).toHaveBeenCalledWith('browser.memoryUsage', true, expect.objectContaining({
|
|
84
|
+
usage: expect.any(Number)
|
|
85
|
+
}), COMPONENTS.BROWSER_FETCH);
|
|
86
|
+
});
|
|
87
|
+
test('当内存使用过高时应该记录警告 (Should log warning when memory usage is high)', () => {
|
|
88
|
+
// 模拟高内存使用情况
|
|
89
|
+
const highMemoryUsage = {
|
|
90
|
+
heapUsed: 900 * 1024 * 1024, // 900MB
|
|
91
|
+
heapTotal: 1024 * 1024 * 1024, // 1GB
|
|
92
|
+
rss: 1.5 * 1024 * 1024 * 1024, // 1.5GB
|
|
93
|
+
external: 200 * 1024 * 1024, // 200MB
|
|
94
|
+
arrayBuffers: 100 * 1024 * 1024 // 100MB
|
|
95
|
+
};
|
|
96
|
+
vi.spyOn(process, 'memoryUsage').mockReturnValue(highMemoryUsage);
|
|
97
|
+
// 调用方法
|
|
98
|
+
BrowserInstance.checkMemoryUsage(true);
|
|
99
|
+
// 验证警告日志被记录
|
|
100
|
+
expect(log).toHaveBeenCalledWith('browser.memoryTooHigh', true, expect.objectContaining({
|
|
101
|
+
heapUsed: expect.any(Number),
|
|
102
|
+
heapTotal: expect.any(Number),
|
|
103
|
+
rss: expect.any(Number)
|
|
104
|
+
}), COMPONENTS.BROWSER_FETCH);
|
|
105
|
+
});
|
|
106
|
+
test('应该在检查间隔内只检查一次内存 (Should check memory only once within interval)', () => {
|
|
107
|
+
// 连续调用两次
|
|
108
|
+
BrowserInstance.checkMemoryUsage(true);
|
|
109
|
+
BrowserInstance.checkMemoryUsage(true);
|
|
110
|
+
// 验证 process.memoryUsage 只被调用一次
|
|
111
|
+
expect(process.memoryUsage).toHaveBeenCalledTimes(1);
|
|
112
|
+
});
|
|
113
|
+
test('应该在超过检查间隔后重新检查内存 (Should check memory again after interval)', () => {
|
|
114
|
+
// 第一次调用
|
|
115
|
+
BrowserInstance.checkMemoryUsage(true);
|
|
116
|
+
// 模拟时间经过
|
|
117
|
+
vi.advanceTimersByTime(60000); // 60秒后
|
|
118
|
+
// 第二次调用
|
|
119
|
+
BrowserInstance.checkMemoryUsage(true);
|
|
120
|
+
// 验证 process.memoryUsage 被调用两次
|
|
121
|
+
expect(process.memoryUsage).toHaveBeenCalledTimes(2);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
describe('浏览器实例管理测试 (Browser Instance Management Tests)', () => {
|
|
125
|
+
test('应该正确获取浏览器实例 (Should get browser instance correctly)', async () => {
|
|
126
|
+
// 调用方法
|
|
127
|
+
const browser = await BrowserInstance.getBrowser(true);
|
|
128
|
+
// 验证 puppeteer-extra.launch 被调用
|
|
129
|
+
expect(mockLaunch).toHaveBeenCalled();
|
|
130
|
+
// 验证返回的浏览器实例
|
|
131
|
+
expect(browser).toBe(mockBrowser);
|
|
132
|
+
// 验证日志被记录
|
|
133
|
+
expect(log).toHaveBeenCalledWith('browser.startingBrowser', true, expect.any(Object), COMPONENTS.BROWSER_FETCH);
|
|
134
|
+
});
|
|
135
|
+
test('应该重用已存在的浏览器实例 (Should reuse existing browser instance)', async () => {
|
|
136
|
+
// 设置浏览器实例
|
|
137
|
+
// @ts-ignore - 访问私有属性用于测试
|
|
138
|
+
BrowserInstance.browser = mockBrowser;
|
|
139
|
+
// 调用方法
|
|
140
|
+
const browser = await BrowserInstance.getBrowser(true);
|
|
141
|
+
// 验证 puppeteer-extra.launch 没有被调用
|
|
142
|
+
expect(mockLaunch).not.toHaveBeenCalled();
|
|
143
|
+
// 验证返回的浏览器实例
|
|
144
|
+
expect(browser).toBe(mockBrowser);
|
|
145
|
+
// 验证日志被记录
|
|
146
|
+
expect(log).toHaveBeenCalledWith('browser.reusingExistingBrowser', true, expect.any(Object), COMPONENTS.BROWSER_FETCH);
|
|
147
|
+
});
|
|
148
|
+
test('应该正确关闭浏览器实例 (Should close browser instance correctly)', async () => {
|
|
149
|
+
// 设置浏览器实例
|
|
150
|
+
// @ts-ignore - 访问私有属性用于测试
|
|
151
|
+
BrowserInstance.browser = mockBrowser;
|
|
152
|
+
// 调用方法
|
|
153
|
+
await BrowserInstance.closeBrowser(true);
|
|
154
|
+
// 验证 browser.close 被调用
|
|
155
|
+
expect(mockBrowser.close).toHaveBeenCalled();
|
|
156
|
+
// 验证浏览器实例被重置
|
|
157
|
+
// @ts-ignore - 访问私有属性用于测试
|
|
158
|
+
expect(BrowserInstance.browser).toBeNull();
|
|
159
|
+
// 验证日志被记录
|
|
160
|
+
expect(log).toHaveBeenCalledWith('browser.closed', true, expect.any(Object), COMPONENTS.BROWSER_FETCH);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
});
|
|
@@ -0,0 +1,211 @@
|
|
|
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 { NodeFetcher } from '../../../src/lib/fetchers/node/NodeFetcher.js';
|
|
7
|
+
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
|
8
|
+
import { HttpClient } from '../../../src/lib/fetchers/node/HttpClient.js';
|
|
9
|
+
// 模拟TurndownService
|
|
10
|
+
vi.mock('turndown', () => {
|
|
11
|
+
return {
|
|
12
|
+
default: vi.fn().mockImplementation(() => {
|
|
13
|
+
return {
|
|
14
|
+
turndown: vi.fn().mockImplementation((html) => {
|
|
15
|
+
if (html.includes('<h1>标题</h1><p>这是Markdown内容</p>')) {
|
|
16
|
+
return '# 标题\n\n这是Markdown内容';
|
|
17
|
+
}
|
|
18
|
+
return 'markdown content';
|
|
19
|
+
}),
|
|
20
|
+
addRule: vi.fn()
|
|
21
|
+
};
|
|
22
|
+
})
|
|
23
|
+
};
|
|
24
|
+
});
|
|
25
|
+
// 模拟JSDOM
|
|
26
|
+
vi.mock('jsdom', () => {
|
|
27
|
+
return {
|
|
28
|
+
JSDOM: vi.fn().mockImplementation((html) => {
|
|
29
|
+
return {
|
|
30
|
+
window: {
|
|
31
|
+
document: {
|
|
32
|
+
body: {
|
|
33
|
+
textContent: html.includes('这是纯文本内容') ? '这是纯文本内容' : 'text content'
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
})
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
// 模拟node-fetch
|
|
42
|
+
vi.mock('node-fetch', () => {
|
|
43
|
+
return {
|
|
44
|
+
default: vi.fn()
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
// 模拟http-proxy-agent
|
|
48
|
+
vi.mock('http-proxy-agent', () => {
|
|
49
|
+
return {
|
|
50
|
+
HttpProxyAgent: class HttpProxyAgent {
|
|
51
|
+
constructor(proxy) { }
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
});
|
|
55
|
+
// 模拟https-proxy-agent
|
|
56
|
+
vi.mock('https-proxy-agent', () => {
|
|
57
|
+
return {
|
|
58
|
+
HttpsProxyAgent: class HttpsProxyAgent {
|
|
59
|
+
constructor(proxy) { }
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
// 模拟child_process
|
|
64
|
+
vi.mock('child_process', () => {
|
|
65
|
+
return {
|
|
66
|
+
execSync: vi.fn().mockImplementation((cmd) => {
|
|
67
|
+
if (cmd.includes('http_proxy')) {
|
|
68
|
+
return 'http_proxy=http://proxy.example.com:8080';
|
|
69
|
+
}
|
|
70
|
+
return '';
|
|
71
|
+
})
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
// 模拟logger
|
|
75
|
+
vi.mock('../../../src/lib/logger.js', () => {
|
|
76
|
+
return {
|
|
77
|
+
log: vi.fn(),
|
|
78
|
+
COMPONENTS: {
|
|
79
|
+
NODE_FETCH: 'node-fetch'
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
// 模拟HttpClient (Mock HttpClient)
|
|
84
|
+
vi.mock('../../../src/lib/fetchers/node/HttpClient.js', () => {
|
|
85
|
+
return {
|
|
86
|
+
HttpClient: {
|
|
87
|
+
fetchWithRedirects: vi.fn()
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
});
|
|
91
|
+
describe('NodeFetcher 类测试 (NodeFetcher Class Tests)', () => {
|
|
92
|
+
let nodeFetcher;
|
|
93
|
+
let requestPayload;
|
|
94
|
+
beforeEach(() => {
|
|
95
|
+
// 创建NodeFetcher实例 (Create NodeFetcher instance)
|
|
96
|
+
nodeFetcher = new NodeFetcher();
|
|
97
|
+
// 重置模拟 (Reset mocks)
|
|
98
|
+
vi.clearAllMocks();
|
|
99
|
+
// 设置请求参数 (Set request parameters)
|
|
100
|
+
requestPayload = {
|
|
101
|
+
url: 'https://example.com',
|
|
102
|
+
headers: { 'User-Agent': 'test-agent' },
|
|
103
|
+
timeout: 5000,
|
|
104
|
+
debug: true
|
|
105
|
+
};
|
|
106
|
+
// 默认模拟成功的响应 (Default mock for successful response)
|
|
107
|
+
vi.mocked(HttpClient.fetchWithRedirects).mockResolvedValue({
|
|
108
|
+
text: vi.fn().mockResolvedValue('<html><body>测试内容</body></html>'),
|
|
109
|
+
json: vi.fn().mockResolvedValue({ name: '测试', value: 123 }),
|
|
110
|
+
status: 200,
|
|
111
|
+
ok: true,
|
|
112
|
+
url: 'https://example.com'
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
describe('html 方法测试 (html Method Tests)', () => {
|
|
116
|
+
it('应该成功获取HTML内容 (should successfully fetch HTML content)', async () => {
|
|
117
|
+
// 模拟响应 (Mock response)
|
|
118
|
+
vi.mocked(HttpClient.fetchWithRedirects).mockResolvedValue({
|
|
119
|
+
text: vi.fn().mockResolvedValue('<html><body>测试内容</body></html>'),
|
|
120
|
+
status: 200,
|
|
121
|
+
ok: true,
|
|
122
|
+
url: 'https://example.com'
|
|
123
|
+
});
|
|
124
|
+
// 调用方法 (Call method)
|
|
125
|
+
const result = await nodeFetcher.html(requestPayload);
|
|
126
|
+
// 验证结果
|
|
127
|
+
expect(result.content[0].text).toBe('<html><body>测试内容</body></html>');
|
|
128
|
+
expect(result.isError).toBe(false);
|
|
129
|
+
expect(HttpClient.fetchWithRedirects).toHaveBeenCalledWith(requestPayload);
|
|
130
|
+
});
|
|
131
|
+
it('应该处理获取HTML时的错误 (should handle errors when fetching HTML)', async () => {
|
|
132
|
+
// 模拟错误 (Mock error)
|
|
133
|
+
vi.mocked(HttpClient.fetchWithRedirects).mockRejectedValue(new Error('连接失败'));
|
|
134
|
+
// 调用方法 (Call method)
|
|
135
|
+
const result = await nodeFetcher.html(requestPayload);
|
|
136
|
+
// 验证错误处理
|
|
137
|
+
expect(result.isError).toBe(true);
|
|
138
|
+
expect(result.content[0].text).toContain('连接失败');
|
|
139
|
+
expect(HttpClient.fetchWithRedirects).toHaveBeenCalledWith(requestPayload);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
describe('json 方法测试 (json Method Tests)', () => {
|
|
143
|
+
it('应该成功获取并解析JSON内容 (should successfully fetch and parse JSON content)', async () => {
|
|
144
|
+
// 模拟JSON响应 (Mock JSON response)
|
|
145
|
+
vi.mocked(HttpClient.fetchWithRedirects).mockResolvedValue({
|
|
146
|
+
text: vi.fn().mockResolvedValue('{"name":"测试","value":123}'),
|
|
147
|
+
json: vi.fn().mockResolvedValue({ name: '测试', value: 123 }),
|
|
148
|
+
status: 200,
|
|
149
|
+
ok: true,
|
|
150
|
+
url: 'https://example.com'
|
|
151
|
+
});
|
|
152
|
+
// 调用方法 (Call method)
|
|
153
|
+
const result = await nodeFetcher.json(requestPayload);
|
|
154
|
+
// 验证结果
|
|
155
|
+
expect(result.content[0].text).toBe('{"name":"测试","value":123}');
|
|
156
|
+
expect(result.isError).toBe(false);
|
|
157
|
+
expect(HttpClient.fetchWithRedirects).toHaveBeenCalledWith(requestPayload);
|
|
158
|
+
});
|
|
159
|
+
it('应该处理无效的JSON内容 (should handle invalid JSON content)', async () => {
|
|
160
|
+
// 模拟无效JSON响应 (Mock invalid JSON response)
|
|
161
|
+
vi.mocked(HttpClient.fetchWithRedirects).mockResolvedValue({
|
|
162
|
+
text: vi.fn().mockResolvedValue('{"invalid json'),
|
|
163
|
+
json: vi.fn().mockRejectedValue(new Error('Invalid JSON')),
|
|
164
|
+
status: 200,
|
|
165
|
+
ok: true,
|
|
166
|
+
url: 'https://example.com'
|
|
167
|
+
});
|
|
168
|
+
// 调用方法 (Call method)
|
|
169
|
+
const result = await nodeFetcher.json(requestPayload);
|
|
170
|
+
// 验证错误处理
|
|
171
|
+
expect(result.isError).toBe(true);
|
|
172
|
+
expect(result.content[0].text).toContain('Invalid JSON');
|
|
173
|
+
expect(HttpClient.fetchWithRedirects).toHaveBeenCalledWith(requestPayload);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
describe('txt 方法测试 (txt Method Tests)', () => {
|
|
177
|
+
it('应该成功获取纯文本内容 (should successfully fetch plain text content)', async () => {
|
|
178
|
+
// 模拟HTML响应 (Mock HTML response)
|
|
179
|
+
const htmlContent = '<html><body>这是纯文本内容</body></html>';
|
|
180
|
+
vi.mocked(HttpClient.fetchWithRedirects).mockResolvedValue({
|
|
181
|
+
text: vi.fn().mockResolvedValue(htmlContent),
|
|
182
|
+
status: 200,
|
|
183
|
+
ok: true,
|
|
184
|
+
url: 'https://example.com'
|
|
185
|
+
});
|
|
186
|
+
// 调用方法 (Call method)
|
|
187
|
+
const result = await nodeFetcher.txt(requestPayload);
|
|
188
|
+
// 验证结果 - NodeFetcher的txt方法直接返回HTML内容,不提取纯文本
|
|
189
|
+
expect(result.content[0].text).toBe(htmlContent);
|
|
190
|
+
expect(result.isError).toBe(false);
|
|
191
|
+
expect(HttpClient.fetchWithRedirects).toHaveBeenCalledWith(requestPayload);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
describe('markdown 方法测试 (markdown Method Tests)', () => {
|
|
195
|
+
it('应该成功获取并转换为Markdown内容 (should successfully fetch and convert to Markdown content)', async () => {
|
|
196
|
+
// 模拟HTML响应 (Mock HTML response)
|
|
197
|
+
vi.mocked(HttpClient.fetchWithRedirects).mockResolvedValue({
|
|
198
|
+
text: vi.fn().mockResolvedValue('<html><body><h1>标题</h1><p>这是Markdown内容</p></body></html>'),
|
|
199
|
+
status: 200,
|
|
200
|
+
ok: true,
|
|
201
|
+
url: 'https://example.com'
|
|
202
|
+
});
|
|
203
|
+
// 调用方法 (Call method)
|
|
204
|
+
const result = await nodeFetcher.markdown(requestPayload);
|
|
205
|
+
// 验证结果
|
|
206
|
+
expect(result.content[0].text).toBe('# 标题\n\n这是Markdown内容');
|
|
207
|
+
expect(result.isError).toBe(false);
|
|
208
|
+
expect(HttpClient.fetchWithRedirects).toHaveBeenCalledWith(requestPayload);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
});
|