@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
|
@@ -5,13 +5,14 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
7
7
|
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
8
|
+
import { vi, describe, test, expect, beforeEach, afterAll } from 'vitest';
|
|
8
9
|
// 保存原始值以便在测试后恢复
|
|
9
10
|
const originalArgv = process.argv;
|
|
10
11
|
const originalExit = process.exit;
|
|
11
12
|
const originalStdoutWrite = process.stdout.write;
|
|
12
13
|
// 模拟 logger 模块
|
|
13
|
-
const mockLog =
|
|
14
|
-
|
|
14
|
+
const mockLog = vi.fn();
|
|
15
|
+
vi.mock('../src/lib/logger.js', () => ({
|
|
15
16
|
log: mockLog,
|
|
16
17
|
COMPONENTS: {
|
|
17
18
|
CLIENT: 'CLIENT',
|
|
@@ -21,27 +22,27 @@ jest.mock('../src/lib/logger.js', () => ({
|
|
|
21
22
|
}
|
|
22
23
|
}));
|
|
23
24
|
// 模拟依赖模块
|
|
24
|
-
|
|
25
|
-
Client:
|
|
26
|
-
connect:
|
|
27
|
-
callTool:
|
|
28
|
-
close:
|
|
25
|
+
vi.mock('@modelcontextprotocol/sdk/client/index.js', () => ({
|
|
26
|
+
Client: vi.fn().mockImplementation(() => ({
|
|
27
|
+
connect: vi.fn().mockResolvedValue(undefined),
|
|
28
|
+
callTool: vi.fn(),
|
|
29
|
+
close: vi.fn().mockResolvedValue(undefined)
|
|
29
30
|
}))
|
|
30
31
|
}));
|
|
31
|
-
|
|
32
|
-
StdioClientTransport:
|
|
32
|
+
vi.mock('@modelcontextprotocol/sdk/client/stdio.js', () => ({
|
|
33
|
+
StdioClientTransport: vi.fn().mockImplementation(() => ({}))
|
|
33
34
|
}));
|
|
34
|
-
|
|
35
|
-
execSync:
|
|
35
|
+
vi.mock('child_process', () => ({
|
|
36
|
+
execSync: vi.fn()
|
|
36
37
|
}));
|
|
37
38
|
// 模拟 process.exit
|
|
38
|
-
|
|
39
|
+
vi.spyOn(process, 'exit').mockImplementation((code) => {
|
|
39
40
|
console.log(`[模拟] process.exit(${code})`);
|
|
40
41
|
return undefined;
|
|
41
42
|
});
|
|
42
43
|
// 创建模拟的客户端模块
|
|
43
44
|
const mockClientModule = {
|
|
44
|
-
responseRequiresBrowser:
|
|
45
|
+
responseRequiresBrowser: vi.fn((response, debug = false) => {
|
|
45
46
|
if (response.isError) {
|
|
46
47
|
const errorText = response.content[0].text.toLowerCase();
|
|
47
48
|
if (debug) {
|
|
@@ -58,7 +59,7 @@ const mockClientModule = {
|
|
|
58
59
|
}
|
|
59
60
|
return false;
|
|
60
61
|
}),
|
|
61
|
-
smartFetch:
|
|
62
|
+
smartFetch: vi.fn(async (params) => {
|
|
62
63
|
const mockClient = new Client({
|
|
63
64
|
name: "fetch-mcp-client",
|
|
64
65
|
version: "1.0.0"
|
|
@@ -84,7 +85,7 @@ const mockClientModule = {
|
|
|
84
85
|
await mockClient.close();
|
|
85
86
|
}
|
|
86
87
|
}),
|
|
87
|
-
main:
|
|
88
|
+
main: vi.fn(() => {
|
|
88
89
|
// 检查命令行参数
|
|
89
90
|
if (process.argv.length < 4) {
|
|
90
91
|
mockLog('client.usageInfo', true, {}, 'CLIENT');
|
|
@@ -107,12 +108,12 @@ const mockClientModule = {
|
|
|
107
108
|
})
|
|
108
109
|
};
|
|
109
110
|
// 模拟 client.ts 模块
|
|
110
|
-
|
|
111
|
+
vi.mock('../src/client.ts', () => mockClientModule);
|
|
111
112
|
describe('client.ts 测试', () => {
|
|
112
113
|
// 在每个测试前重置模拟
|
|
113
114
|
beforeEach(() => {
|
|
114
|
-
|
|
115
|
-
|
|
115
|
+
vi.resetModules();
|
|
116
|
+
vi.clearAllMocks();
|
|
116
117
|
});
|
|
117
118
|
// 在所有测试后恢复原始值
|
|
118
119
|
afterAll(() => {
|
|
@@ -121,37 +122,19 @@ describe('client.ts 测试', () => {
|
|
|
121
122
|
});
|
|
122
123
|
describe('responseRequiresBrowser 函数测试', () => {
|
|
123
124
|
test('应该在响应包含 403 错误时返回 true', () => {
|
|
124
|
-
// 模拟依赖
|
|
125
|
-
jest.doMock('../src/lib/logger.js', () => ({
|
|
126
|
-
log: jest.fn(),
|
|
127
|
-
COMPONENTS: {
|
|
128
|
-
CLIENT: 'CLIENT'
|
|
129
|
-
}
|
|
130
|
-
}));
|
|
131
|
-
// 导入被测试的模块
|
|
132
|
-
const { responseRequiresBrowser } = require('../src/client.js');
|
|
133
125
|
const response = {
|
|
134
126
|
isError: true,
|
|
135
127
|
content: [{ text: '403 Forbidden' }]
|
|
136
128
|
};
|
|
137
|
-
const result = responseRequiresBrowser(response, true);
|
|
129
|
+
const result = mockClientModule.responseRequiresBrowser(response, true);
|
|
138
130
|
expect(result).toBe(true);
|
|
139
131
|
});
|
|
140
132
|
test('应该在响应不是错误时返回 false', () => {
|
|
141
|
-
// 模拟依赖
|
|
142
|
-
jest.doMock('../src/lib/logger.js', () => ({
|
|
143
|
-
log: jest.fn(),
|
|
144
|
-
COMPONENTS: {
|
|
145
|
-
CLIENT: 'CLIENT'
|
|
146
|
-
}
|
|
147
|
-
}));
|
|
148
|
-
// 导入被测试的模块
|
|
149
|
-
const { responseRequiresBrowser } = require('../src/client.js');
|
|
150
133
|
const response = {
|
|
151
134
|
isError: false,
|
|
152
135
|
content: [{ text: 'Success' }]
|
|
153
136
|
};
|
|
154
|
-
const result = responseRequiresBrowser(response);
|
|
137
|
+
const result = mockClientModule.responseRequiresBrowser(response);
|
|
155
138
|
expect(result).toBe(false);
|
|
156
139
|
});
|
|
157
140
|
});
|
|
@@ -163,11 +146,11 @@ describe('client.ts 测试', () => {
|
|
|
163
146
|
content: [{ text: '<html><body>Success</body></html>' }]
|
|
164
147
|
};
|
|
165
148
|
// 设置 callTool 的返回值
|
|
166
|
-
const mockCallTool =
|
|
149
|
+
const mockCallTool = vi.fn().mockResolvedValue(mockResult);
|
|
167
150
|
Client.mockImplementation(() => ({
|
|
168
|
-
connect:
|
|
151
|
+
connect: vi.fn().mockResolvedValue(undefined),
|
|
169
152
|
callTool: mockCallTool,
|
|
170
|
-
close:
|
|
153
|
+
close: vi.fn().mockResolvedValue(undefined)
|
|
171
154
|
}));
|
|
172
155
|
// 调用函数
|
|
173
156
|
const result = await mockClientModule.smartFetch({
|
|
@@ -189,10 +172,10 @@ describe('client.ts 测试', () => {
|
|
|
189
172
|
});
|
|
190
173
|
test('应该在任何情况下都关闭客户端连接', async () => {
|
|
191
174
|
// 模拟 callTool 抛出错误
|
|
192
|
-
const mockClose =
|
|
175
|
+
const mockClose = vi.fn().mockResolvedValue(undefined);
|
|
193
176
|
Client.mockImplementation(() => ({
|
|
194
|
-
connect:
|
|
195
|
-
callTool:
|
|
177
|
+
connect: vi.fn().mockResolvedValue(undefined),
|
|
178
|
+
callTool: vi.fn().mockRejectedValue(new Error('Test error')),
|
|
196
179
|
close: mockClose
|
|
197
180
|
}));
|
|
198
181
|
// 调用函数并捕获错误
|
|
@@ -206,45 +189,23 @@ describe('client.ts 测试', () => {
|
|
|
206
189
|
});
|
|
207
190
|
describe('main 函数测试', () => {
|
|
208
191
|
test('应该在参数不足时显示使用信息并退出', () => {
|
|
209
|
-
//
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
mockClientModule.main();
|
|
218
|
-
// 验证 log 函数被调用
|
|
219
|
-
expect(mockLog).toHaveBeenCalledWith('client.usageInfo', true, {}, 'CLIENT');
|
|
220
|
-
expect(mockLog).toHaveBeenCalledWith('client.exampleUsage', true, {}, 'CLIENT');
|
|
221
|
-
// 验证 process.exit 被调用
|
|
222
|
-
expect(process.exit).toHaveBeenCalledWith(1);
|
|
223
|
-
}
|
|
224
|
-
finally {
|
|
225
|
-
// 恢复原始的 process.argv
|
|
226
|
-
process.argv = testArgv;
|
|
227
|
-
}
|
|
192
|
+
// 设置命令行参数
|
|
193
|
+
process.argv = ['node', 'client.js'];
|
|
194
|
+
// 调用函数
|
|
195
|
+
mockClientModule.main();
|
|
196
|
+
// 验证日志和退出
|
|
197
|
+
expect(mockLog).toHaveBeenCalledWith('client.usageInfo', true, {}, 'CLIENT');
|
|
198
|
+
expect(mockLog).toHaveBeenCalledWith('client.exampleUsage', true, {}, 'CLIENT');
|
|
199
|
+
expect(process.exit).toHaveBeenCalledWith(1);
|
|
228
200
|
});
|
|
229
201
|
test('应该在 JSON 解析失败时显示错误并退出', () => {
|
|
230
|
-
//
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
// 调用 main 函数
|
|
238
|
-
mockClientModule.main();
|
|
239
|
-
// 验证 log 函数被调用
|
|
240
|
-
expect(mockLog).toHaveBeenCalledWith('client.invalidJson', true, {}, 'CLIENT');
|
|
241
|
-
// 验证 process.exit 被调用
|
|
242
|
-
expect(process.exit).toHaveBeenCalledWith(1);
|
|
243
|
-
}
|
|
244
|
-
finally {
|
|
245
|
-
// 恢复原始的 process.argv
|
|
246
|
-
process.argv = testArgv;
|
|
247
|
-
}
|
|
202
|
+
// 设置命令行参数
|
|
203
|
+
process.argv = ['node', 'client.js', 'fetch_html', 'invalid-json'];
|
|
204
|
+
// 调用函数
|
|
205
|
+
mockClientModule.main();
|
|
206
|
+
// 验证日志和退出
|
|
207
|
+
expect(mockLog).toHaveBeenCalledWith('client.invalidJson', true, {}, 'CLIENT');
|
|
208
|
+
expect(process.exit).toHaveBeenCalledWith(1);
|
|
248
209
|
});
|
|
249
210
|
});
|
|
250
211
|
});
|
|
@@ -0,0 +1,70 @@
|
|
|
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, beforeEach, vi } from 'vitest';
|
|
7
|
+
import { fetchHtml, fetchJson, fetchTxt, fetchMarkdown } from '../src/lib/fetch.js';
|
|
8
|
+
import { Fetcher } from '../src/lib/fetchers/index.js';
|
|
9
|
+
// Mock Fetcher
|
|
10
|
+
vi.mock('../src/lib/fetchers/index.js', () => ({
|
|
11
|
+
Fetcher: {
|
|
12
|
+
html: vi.fn(),
|
|
13
|
+
json: vi.fn(),
|
|
14
|
+
txt: vi.fn(),
|
|
15
|
+
markdown: vi.fn()
|
|
16
|
+
}
|
|
17
|
+
}));
|
|
18
|
+
describe('fetch 函数测试 (fetch functions tests)', () => {
|
|
19
|
+
// 模拟响应
|
|
20
|
+
const mockResponse = {
|
|
21
|
+
content: [{ type: 'text', text: '测试内容' }],
|
|
22
|
+
isError: false
|
|
23
|
+
};
|
|
24
|
+
// 测试请求参数
|
|
25
|
+
const testPayload = {
|
|
26
|
+
url: 'https://example.com',
|
|
27
|
+
debug: true
|
|
28
|
+
};
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
// 重置所有 mock
|
|
31
|
+
vi.clearAllMocks();
|
|
32
|
+
// 设置 Fetcher 方法的返回值
|
|
33
|
+
Fetcher.html.mockResolvedValue(mockResponse);
|
|
34
|
+
Fetcher.json.mockResolvedValue(mockResponse);
|
|
35
|
+
Fetcher.txt.mockResolvedValue(mockResponse);
|
|
36
|
+
Fetcher.markdown.mockResolvedValue(mockResponse);
|
|
37
|
+
});
|
|
38
|
+
test('fetchHtml 应该调用 Fetcher.html (fetchHtml should call Fetcher.html)', async () => {
|
|
39
|
+
// 调用方法
|
|
40
|
+
const result = await fetchHtml(testPayload);
|
|
41
|
+
// 验证 Fetcher.html 被调用
|
|
42
|
+
expect(Fetcher.html).toHaveBeenCalledWith(testPayload);
|
|
43
|
+
// 验证返回结果
|
|
44
|
+
expect(result).toBe(mockResponse);
|
|
45
|
+
});
|
|
46
|
+
test('fetchJson 应该调用 Fetcher.json (fetchJson should call Fetcher.json)', async () => {
|
|
47
|
+
// 调用方法
|
|
48
|
+
const result = await fetchJson(testPayload);
|
|
49
|
+
// 验证 Fetcher.json 被调用
|
|
50
|
+
expect(Fetcher.json).toHaveBeenCalledWith(testPayload);
|
|
51
|
+
// 验证返回结果
|
|
52
|
+
expect(result).toBe(mockResponse);
|
|
53
|
+
});
|
|
54
|
+
test('fetchTxt 应该调用 Fetcher.txt (fetchTxt should call Fetcher.txt)', async () => {
|
|
55
|
+
// 调用方法
|
|
56
|
+
const result = await fetchTxt(testPayload);
|
|
57
|
+
// 验证 Fetcher.txt 被调用
|
|
58
|
+
expect(Fetcher.txt).toHaveBeenCalledWith(testPayload);
|
|
59
|
+
// 验证返回结果
|
|
60
|
+
expect(result).toBe(mockResponse);
|
|
61
|
+
});
|
|
62
|
+
test('fetchMarkdown 应该调用 Fetcher.markdown (fetchMarkdown should call Fetcher.markdown)', async () => {
|
|
63
|
+
// 调用方法
|
|
64
|
+
const result = await fetchMarkdown(testPayload);
|
|
65
|
+
// 验证 Fetcher.markdown 被调用
|
|
66
|
+
expect(Fetcher.markdown).toHaveBeenCalledWith(testPayload);
|
|
67
|
+
// 验证返回结果
|
|
68
|
+
expect(result).toBe(mockResponse);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -0,0 +1,108 @@
|
|
|
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, beforeEach, vi } from 'vitest';
|
|
7
|
+
import { Fetcher } from '../../src/lib/fetchers/index.js';
|
|
8
|
+
import { FetcherFactory } from '../../src/lib/fetchers/FetcherFactory.js';
|
|
9
|
+
import { log, COMPONENTS } from '../../src/lib/logger.js';
|
|
10
|
+
// Mock FetcherFactory
|
|
11
|
+
vi.mock('../../src/lib/fetchers/FetcherFactory.js', () => ({
|
|
12
|
+
FetcherFactory: {
|
|
13
|
+
createFetcher: vi.fn()
|
|
14
|
+
}
|
|
15
|
+
}));
|
|
16
|
+
// Mock logger
|
|
17
|
+
vi.mock('../../src/lib/logger.js', () => ({
|
|
18
|
+
log: vi.fn(),
|
|
19
|
+
COMPONENTS: {
|
|
20
|
+
SERVER: 'SERVER'
|
|
21
|
+
}
|
|
22
|
+
}));
|
|
23
|
+
describe('Fetcher 类测试 (Fetcher Class Tests)', () => {
|
|
24
|
+
// 模拟 fetcher 实例
|
|
25
|
+
const mockFetcher = {
|
|
26
|
+
html: vi.fn(),
|
|
27
|
+
json: vi.fn(),
|
|
28
|
+
txt: vi.fn(),
|
|
29
|
+
markdown: vi.fn()
|
|
30
|
+
};
|
|
31
|
+
// 模拟响应
|
|
32
|
+
const mockResponse = {
|
|
33
|
+
content: [{ type: 'text', text: '测试内容' }],
|
|
34
|
+
isError: false
|
|
35
|
+
};
|
|
36
|
+
// 测试请求参数
|
|
37
|
+
const testPayload = {
|
|
38
|
+
url: 'https://example.com',
|
|
39
|
+
debug: true
|
|
40
|
+
};
|
|
41
|
+
beforeEach(() => {
|
|
42
|
+
// 重置所有 mock
|
|
43
|
+
vi.clearAllMocks();
|
|
44
|
+
// 设置 FetcherFactory.createFetcher 的返回值
|
|
45
|
+
FetcherFactory.createFetcher.mockReturnValue(mockFetcher);
|
|
46
|
+
// 设置 mockFetcher 方法的返回值
|
|
47
|
+
mockFetcher.html.mockResolvedValue(mockResponse);
|
|
48
|
+
mockFetcher.json.mockResolvedValue(mockResponse);
|
|
49
|
+
mockFetcher.txt.mockResolvedValue(mockResponse);
|
|
50
|
+
mockFetcher.markdown.mockResolvedValue(mockResponse);
|
|
51
|
+
});
|
|
52
|
+
test('应该正确获取HTML内容 (Should fetch HTML content correctly)', async () => {
|
|
53
|
+
// 调用方法
|
|
54
|
+
const result = await Fetcher.html(testPayload);
|
|
55
|
+
// 验证 FetcherFactory.createFetcher 被调用
|
|
56
|
+
expect(FetcherFactory.createFetcher).toHaveBeenCalledWith(testPayload);
|
|
57
|
+
// 验证 mockFetcher.html 被调用
|
|
58
|
+
expect(mockFetcher.html).toHaveBeenCalledWith(testPayload);
|
|
59
|
+
// 验证返回结果
|
|
60
|
+
expect(result).toBe(mockResponse);
|
|
61
|
+
// 验证日志被记录
|
|
62
|
+
expect(log).toHaveBeenCalledWith('fetcher.fetchingHtml', true, expect.objectContaining({
|
|
63
|
+
url: testPayload.url
|
|
64
|
+
}), COMPONENTS.SERVER);
|
|
65
|
+
});
|
|
66
|
+
test('应该正确获取JSON内容 (Should fetch JSON content correctly)', async () => {
|
|
67
|
+
// 调用方法
|
|
68
|
+
const result = await Fetcher.json(testPayload);
|
|
69
|
+
// 验证 FetcherFactory.createFetcher 被调用
|
|
70
|
+
expect(FetcherFactory.createFetcher).toHaveBeenCalledWith(testPayload);
|
|
71
|
+
// 验证 mockFetcher.json 被调用
|
|
72
|
+
expect(mockFetcher.json).toHaveBeenCalledWith(testPayload);
|
|
73
|
+
// 验证返回结果
|
|
74
|
+
expect(result).toBe(mockResponse);
|
|
75
|
+
// 验证日志被记录
|
|
76
|
+
expect(log).toHaveBeenCalledWith('fetcher.fetchingJson', true, expect.objectContaining({
|
|
77
|
+
url: testPayload.url
|
|
78
|
+
}), COMPONENTS.SERVER);
|
|
79
|
+
});
|
|
80
|
+
test('应该正确获取纯文本内容 (Should fetch plain text content correctly)', async () => {
|
|
81
|
+
// 调用方法
|
|
82
|
+
const result = await Fetcher.txt(testPayload);
|
|
83
|
+
// 验证 FetcherFactory.createFetcher 被调用
|
|
84
|
+
expect(FetcherFactory.createFetcher).toHaveBeenCalledWith(testPayload);
|
|
85
|
+
// 验证 mockFetcher.txt 被调用
|
|
86
|
+
expect(mockFetcher.txt).toHaveBeenCalledWith(testPayload);
|
|
87
|
+
// 验证返回结果
|
|
88
|
+
expect(result).toBe(mockResponse);
|
|
89
|
+
// 验证日志被记录
|
|
90
|
+
expect(log).toHaveBeenCalledWith('fetcher.fetchingTxt', true, expect.objectContaining({
|
|
91
|
+
url: testPayload.url
|
|
92
|
+
}), COMPONENTS.SERVER);
|
|
93
|
+
});
|
|
94
|
+
test('应该正确获取Markdown内容 (Should fetch Markdown content correctly)', async () => {
|
|
95
|
+
// 调用方法
|
|
96
|
+
const result = await Fetcher.markdown(testPayload);
|
|
97
|
+
// 验证 FetcherFactory.createFetcher 被调用
|
|
98
|
+
expect(FetcherFactory.createFetcher).toHaveBeenCalledWith(testPayload);
|
|
99
|
+
// 验证 mockFetcher.markdown 被调用
|
|
100
|
+
expect(mockFetcher.markdown).toHaveBeenCalledWith(testPayload);
|
|
101
|
+
// 验证返回结果
|
|
102
|
+
expect(result).toBe(mockResponse);
|
|
103
|
+
// 验证日志被记录
|
|
104
|
+
expect(log).toHaveBeenCalledWith('fetcher.fetchingMarkdown', true, expect.objectContaining({
|
|
105
|
+
url: testPayload.url
|
|
106
|
+
}), COMPONENTS.SERVER);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
@@ -0,0 +1,195 @@
|
|
|
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/fetchers/browser/BrowserFetcher.js';
|
|
7
|
+
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
|
8
|
+
// Mock Puppeteer
|
|
9
|
+
vi.mock('puppeteer', () => {
|
|
10
|
+
return {
|
|
11
|
+
default: {
|
|
12
|
+
launch: vi.fn().mockResolvedValue({
|
|
13
|
+
newPage: vi.fn().mockResolvedValue({
|
|
14
|
+
goto: vi.fn().mockResolvedValue({}),
|
|
15
|
+
waitForSelector: vi.fn().mockResolvedValue({}),
|
|
16
|
+
content: vi.fn().mockResolvedValue('<html><body>测试内容</body></html>'),
|
|
17
|
+
evaluate: vi.fn(),
|
|
18
|
+
close: vi.fn()
|
|
19
|
+
}),
|
|
20
|
+
close: vi.fn()
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
});
|
|
25
|
+
// Mock puppeteer-extra
|
|
26
|
+
vi.mock('puppeteer-extra', () => {
|
|
27
|
+
return {
|
|
28
|
+
__esModule: true,
|
|
29
|
+
default: {
|
|
30
|
+
use: vi.fn().mockReturnThis(),
|
|
31
|
+
launch: vi.fn()
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
// Mock puppeteer-extra-plugin-stealth
|
|
36
|
+
vi.mock('puppeteer-extra-plugin-stealth', () => {
|
|
37
|
+
return {
|
|
38
|
+
__esModule: true,
|
|
39
|
+
default: vi.fn().mockReturnValue({})
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
// Mock JSDOM
|
|
43
|
+
vi.mock('jsdom', () => {
|
|
44
|
+
return {
|
|
45
|
+
JSDOM: class {
|
|
46
|
+
window;
|
|
47
|
+
constructor(html) {
|
|
48
|
+
this.window = {
|
|
49
|
+
document: {
|
|
50
|
+
body: {
|
|
51
|
+
textContent: '这是纯文本内容'
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
// Mock TurndownService
|
|
60
|
+
vi.mock('turndown', () => {
|
|
61
|
+
return {
|
|
62
|
+
default: class TurndownService {
|
|
63
|
+
constructor(options) { }
|
|
64
|
+
addRule(name, rule) { }
|
|
65
|
+
turndown(html) {
|
|
66
|
+
return '# 标题\n\n这是Markdown内容';
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
// Mock child_process
|
|
72
|
+
vi.mock('child_process', () => {
|
|
73
|
+
return {
|
|
74
|
+
execSync: vi.fn().mockImplementation((cmd) => {
|
|
75
|
+
if (cmd.includes('http_proxy')) {
|
|
76
|
+
return 'http_proxy=http://proxy.example.com:8080';
|
|
77
|
+
}
|
|
78
|
+
return '';
|
|
79
|
+
})
|
|
80
|
+
};
|
|
81
|
+
});
|
|
82
|
+
// Mock logger
|
|
83
|
+
vi.mock('../../../src/lib/logger.js', () => {
|
|
84
|
+
return {
|
|
85
|
+
log: vi.fn(),
|
|
86
|
+
COMPONENTS: {
|
|
87
|
+
BROWSER_FETCH: 'browser-fetch'
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
});
|
|
91
|
+
// Mock BrowserInstance
|
|
92
|
+
vi.mock('../../../src/lib/fetchers/browser/BrowserInstance.js', () => {
|
|
93
|
+
return {
|
|
94
|
+
BrowserInstance: {
|
|
95
|
+
getInstance: vi.fn().mockResolvedValue({
|
|
96
|
+
newPage: vi.fn().mockResolvedValue({
|
|
97
|
+
goto: vi.fn().mockResolvedValue({}),
|
|
98
|
+
waitForSelector: vi.fn().mockResolvedValue({}),
|
|
99
|
+
content: vi.fn().mockResolvedValue('<html><body>测试内容</body></html>'),
|
|
100
|
+
evaluate: vi.fn(),
|
|
101
|
+
close: vi.fn()
|
|
102
|
+
}),
|
|
103
|
+
close: vi.fn()
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
});
|
|
108
|
+
describe('BrowserFetcher 类测试 (BrowserFetcher Class Tests)', () => {
|
|
109
|
+
let browserFetcher;
|
|
110
|
+
let requestPayload;
|
|
111
|
+
beforeEach(() => {
|
|
112
|
+
// 创建BrowserFetcher实例 (Create BrowserFetcher instance)
|
|
113
|
+
browserFetcher = new BrowserFetcher();
|
|
114
|
+
// 重置模拟 (Reset mocks)
|
|
115
|
+
vi.clearAllMocks();
|
|
116
|
+
// 设置请求参数 (Set request parameters)
|
|
117
|
+
requestPayload = {
|
|
118
|
+
url: 'https://example.com',
|
|
119
|
+
headers: { 'User-Agent': 'test-agent' },
|
|
120
|
+
timeout: 5000,
|
|
121
|
+
waitForSelector: 'body',
|
|
122
|
+
waitForTimeout: 1000,
|
|
123
|
+
debug: true
|
|
124
|
+
};
|
|
125
|
+
// 模拟静态fetch方法 (Mock static fetch method)
|
|
126
|
+
vi.spyOn(BrowserFetcher, 'fetch').mockResolvedValue({
|
|
127
|
+
content: [{ type: 'text', text: '<html><body>测试内容</body></html>' }],
|
|
128
|
+
isError: false
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
describe('html 方法测试 (html Method Tests)', () => {
|
|
132
|
+
it('应该成功获取HTML内容 (should successfully fetch HTML content)', async () => {
|
|
133
|
+
// 调用方法 (Call method)
|
|
134
|
+
const result = await browserFetcher.html(requestPayload);
|
|
135
|
+
// 验证结果 (Verify result)
|
|
136
|
+
expect(result.isError).toBe(false);
|
|
137
|
+
expect(result.content[0].text).toBe('<html><body>测试内容</body></html>');
|
|
138
|
+
});
|
|
139
|
+
it('应该处理获取HTML时的错误 (should handle errors when fetching HTML)', async () => {
|
|
140
|
+
// 模拟错误 (Mock error)
|
|
141
|
+
vi.spyOn(BrowserFetcher, 'fetch').mockResolvedValue({
|
|
142
|
+
content: [{ type: 'text', text: 'Error fetching HTML: Error: 连接失败' }],
|
|
143
|
+
isError: true
|
|
144
|
+
});
|
|
145
|
+
// 调用方法 (Call method)
|
|
146
|
+
const result = await browserFetcher.html(requestPayload);
|
|
147
|
+
// 验证结果 (Verify result)
|
|
148
|
+
expect(result.isError).toBe(true);
|
|
149
|
+
expect(result.content[0].text).toContain('连接失败');
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
describe('json 方法测试 (json Method Tests)', () => {
|
|
153
|
+
it('应该成功获取JSON内容 (should successfully fetch JSON content)', async () => {
|
|
154
|
+
// 模拟JSON内容 (Mock JSON content)
|
|
155
|
+
vi.spyOn(BrowserFetcher, 'fetch').mockResolvedValue({
|
|
156
|
+
content: [{ type: 'text', text: '{"name":"测试","value":123}' }],
|
|
157
|
+
isError: false
|
|
158
|
+
});
|
|
159
|
+
// 调用方法 (Call method)
|
|
160
|
+
const result = await browserFetcher.json(requestPayload);
|
|
161
|
+
// 验证结果 (Verify result)
|
|
162
|
+
expect(result.isError).toBe(false);
|
|
163
|
+
expect(result.content[0].text).toBe('{"name":"测试","value":123}');
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
describe('txt 方法测试 (txt Method Tests)', () => {
|
|
167
|
+
it('应该成功获取纯文本内容 (should successfully fetch plain text content)', async () => {
|
|
168
|
+
// 模拟HTML内容 (Mock HTML content)
|
|
169
|
+
vi.spyOn(BrowserFetcher, 'fetch').mockResolvedValue({
|
|
170
|
+
content: [{ type: 'text', text: '<html><body>这是纯文本内容</body></html>' }],
|
|
171
|
+
isError: false
|
|
172
|
+
});
|
|
173
|
+
// 调用方法 (Call method)
|
|
174
|
+
const result = await browserFetcher.txt(requestPayload);
|
|
175
|
+
// 验证结果 (Verify result)
|
|
176
|
+
expect(result.isError).toBe(false);
|
|
177
|
+
expect(result.content[0].text).toContain('这是纯文本内容');
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
describe('markdown 方法测试 (markdown Method Tests)', () => {
|
|
181
|
+
it('应该成功获取Markdown内容 (should successfully fetch Markdown content)', async () => {
|
|
182
|
+
// 模拟HTML内容 (Mock HTML content)
|
|
183
|
+
vi.spyOn(BrowserFetcher, 'fetch').mockResolvedValue({
|
|
184
|
+
content: [{ type: 'text', text: '<html><body><h1>标题</h1><p>这是Markdown内容</p></body></html>' }],
|
|
185
|
+
isError: false
|
|
186
|
+
});
|
|
187
|
+
// 调用方法 (Call method)
|
|
188
|
+
const result = await browserFetcher.markdown(requestPayload);
|
|
189
|
+
// 验证结果 (Verify result)
|
|
190
|
+
expect(result.isError).toBe(false);
|
|
191
|
+
expect(result.content[0].text).toContain('标题');
|
|
192
|
+
expect(result.content[0].text).toContain('这是Markdown内容');
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
});
|