@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.
Files changed (216) hide show
  1. package/README.md +8 -8
  2. package/README.zh.md +16 -8
  3. package/dist/i18n-test-report.json +8 -0
  4. package/dist/i18n-unused-keys-report.json +8 -0
  5. package/dist/src/client.js +70 -37
  6. package/dist/src/index.js +0 -0
  7. package/dist/src/lib/BrowserFetcher.js +12 -12
  8. package/dist/src/lib/NodeFetcher.js +22 -22
  9. package/dist/src/lib/fetch.js +40 -0
  10. package/dist/src/lib/fetchers/FetcherFactory.js +43 -0
  11. package/dist/src/lib/fetchers/browser/BrowserFetcher.js +381 -0
  12. package/dist/src/lib/fetchers/browser/BrowserInstance.js +180 -0
  13. package/dist/src/lib/fetchers/browser/PageOperations.js +168 -0
  14. package/dist/src/lib/fetchers/common/types.js +41 -0
  15. package/dist/src/lib/fetchers/common/utils.js +141 -0
  16. package/dist/src/lib/fetchers/index.js +62 -0
  17. package/dist/src/lib/fetchers/node/HttpClient.js +239 -0
  18. package/dist/src/lib/fetchers/node/NodeFetcher.js +180 -0
  19. package/dist/src/lib/i18n/index.js +105 -17
  20. package/dist/src/lib/i18n/keys/base.js +41 -0
  21. package/dist/src/lib/i18n/keys/browser.js +91 -0
  22. package/dist/src/lib/i18n/keys/client.js +68 -0
  23. package/dist/src/lib/i18n/keys/errors.js +49 -0
  24. package/dist/src/lib/i18n/keys/factory.js +46 -0
  25. package/dist/src/lib/i18n/keys/fetcher.js +87 -0
  26. package/dist/src/lib/i18n/keys/index.js +16 -0
  27. package/dist/src/lib/i18n/keys/node.js +69 -0
  28. package/dist/src/lib/i18n/keys/prompts.js +79 -0
  29. package/dist/src/lib/i18n/keys/resources.js +53 -0
  30. package/dist/src/lib/i18n/keys/server.js +67 -0
  31. package/dist/src/lib/i18n/keys/tools.js +39 -0
  32. package/dist/src/lib/i18n/keys.js +488 -119
  33. package/dist/src/lib/i18n/locales/en/browser.js +67 -57
  34. package/dist/src/lib/i18n/locales/en/client.js +57 -33
  35. package/dist/src/lib/i18n/locales/en/errors.js +36 -12
  36. package/dist/src/lib/i18n/locales/en/factory.js +38 -0
  37. package/dist/src/lib/i18n/locales/en/fetcher.js +8 -2
  38. package/dist/src/lib/i18n/locales/en/index.js +10 -8
  39. package/dist/src/lib/i18n/locales/en/node.js +57 -35
  40. package/dist/src/lib/i18n/locales/en/prompts.js +42 -65
  41. package/dist/src/lib/i18n/locales/en/resources.js +52 -55
  42. package/dist/src/lib/i18n/locales/en/server.js +49 -24
  43. package/dist/src/lib/i18n/locales/en/tools.js +27 -19
  44. package/dist/src/lib/i18n/locales/zh/browser.js +71 -62
  45. package/dist/src/lib/i18n/locales/zh/client.js +57 -32
  46. package/dist/src/lib/i18n/locales/zh/errors.js +36 -12
  47. package/dist/src/lib/i18n/locales/zh/factory.js +38 -0
  48. package/dist/src/lib/i18n/locales/zh/fetcher.js +36 -30
  49. package/dist/src/lib/i18n/locales/zh/index.js +10 -8
  50. package/dist/src/lib/i18n/locales/zh/node.js +61 -36
  51. package/dist/src/lib/i18n/locales/zh/prompts.js +35 -57
  52. package/dist/src/lib/i18n/locales/zh/resources.js +52 -55
  53. package/dist/src/lib/i18n/locales/zh/server.js +51 -25
  54. package/dist/src/lib/i18n/locales/zh/tools.js +27 -19
  55. package/dist/src/lib/i18n/logger.js +90 -19
  56. package/dist/src/lib/logger.js +135 -14
  57. package/dist/src/lib/server/browser.js +42 -10
  58. package/dist/src/lib/server/fetcher.js +87 -37
  59. package/dist/src/lib/server/index.js +2 -2
  60. package/dist/src/lib/server/prompts.js +1 -1
  61. package/dist/src/lib/server/resources.js +1 -1
  62. package/dist/src/lib/server/tools.js +46 -10
  63. package/dist/src/lib/utils/ErrorHandler.js +125 -0
  64. package/dist/src/mcp-server.js +0 -0
  65. package/dist/src/test-i18n.js +139 -0
  66. package/dist/tests/BrowserFetcher.test.js +0 -0
  67. package/dist/tests/NodeFetcher.test.js +0 -0
  68. package/dist/tests/client.test.js +42 -81
  69. package/dist/tests/fetch.test.js +70 -0
  70. package/dist/tests/fetchers/Fetcher.test.js +108 -0
  71. package/dist/tests/fetchers/browser/BrowserFetcher.test.js +195 -0
  72. package/dist/tests/fetchers/browser/BrowserInstance.test.js +163 -0
  73. package/dist/tests/fetchers/node/NodeFetcher.test.js +211 -0
  74. package/dist/tests/i18n-missing-keys.js +364 -0
  75. package/dist/tests/i18n-remove-unused-keys.js +236 -0
  76. package/dist/tests/i18n-test-report.json +2004 -0
  77. package/dist/tests/i18n-unused-keys.js +425 -0
  78. package/dist/tests/i18n.test.js +6 -7
  79. package/dist/tests/logger.test.js +14 -13
  80. package/dist/tests/mcp-server.test.js +7 -7
  81. package/dist/tests/server/browser.test.js +25 -21
  82. package/dist/tests/server/fetcher.test.js +58 -101
  83. package/dist/tests/server/index.test.js +30 -30
  84. package/dist/tests/server/prompts.test.js +36 -33
  85. package/dist/tests/server/resources.test.js +120 -84
  86. package/dist/tests/server/tools.test.js +7 -6
  87. package/dist/tests/setup.js +36 -0
  88. package/dist/tests/src/lib/i18n/index.js +108 -0
  89. package/dist/tests/src/lib/i18n/keys/base.js +47 -0
  90. package/dist/tests/src/lib/i18n/keys/browser.js +93 -0
  91. package/dist/tests/src/lib/i18n/keys/client.js +70 -0
  92. package/dist/tests/src/lib/i18n/keys/errors.js +34 -0
  93. package/dist/tests/src/lib/i18n/keys/fetcher.js +84 -0
  94. package/dist/tests/src/lib/i18n/keys/index.js +31 -0
  95. package/dist/tests/src/lib/i18n/keys/node.js +56 -0
  96. package/dist/tests/src/lib/i18n/keys/prompts.js +82 -0
  97. package/dist/tests/src/lib/i18n/keys/resources.js +50 -0
  98. package/dist/tests/src/lib/i18n/keys/server.js +64 -0
  99. package/dist/tests/src/lib/i18n/keys/tools.js +34 -0
  100. package/dist/tests/src/lib/i18n/locales/en/browser.js +88 -0
  101. package/dist/tests/src/lib/i18n/locales/en/client.js +66 -0
  102. package/dist/tests/src/lib/i18n/locales/en/errors.js +28 -0
  103. package/dist/tests/src/lib/i18n/locales/en/fetcher.js +71 -0
  104. package/dist/tests/src/lib/i18n/locales/en/index.js +29 -0
  105. package/dist/tests/src/lib/i18n/locales/en/node.js +51 -0
  106. package/dist/tests/src/lib/i18n/locales/en/prompts.js +52 -0
  107. package/dist/tests/src/lib/i18n/locales/en/resources.js +50 -0
  108. package/dist/tests/src/lib/i18n/locales/en/server.js +56 -0
  109. package/dist/tests/src/lib/i18n/locales/en/tools.js +28 -0
  110. package/dist/tests/src/lib/i18n/locales/zh/browser.js +87 -0
  111. package/dist/tests/src/lib/i18n/locales/zh/client.js +66 -0
  112. package/dist/tests/src/lib/i18n/locales/zh/errors.js +28 -0
  113. package/dist/tests/src/lib/i18n/locales/zh/fetcher.js +71 -0
  114. package/dist/tests/src/lib/i18n/locales/zh/index.js +29 -0
  115. package/dist/tests/src/lib/i18n/locales/zh/node.js +51 -0
  116. package/dist/tests/src/lib/i18n/locales/zh/prompts.js +53 -0
  117. package/dist/tests/src/lib/i18n/locales/zh/resources.js +50 -0
  118. package/dist/tests/src/lib/i18n/locales/zh/server.js +57 -0
  119. package/dist/tests/src/lib/i18n/locales/zh/tools.js +28 -0
  120. package/dist/tests/src/lib/i18n/logger.js +114 -0
  121. package/dist/tests/src/lib/logger.js +181 -0
  122. package/dist/tests/test-direct-client.js +0 -0
  123. package/dist/tests/test-i18n.js +530 -0
  124. package/dist/tests/test-mcp-methods.js +1 -1
  125. package/dist/tests/test-mcp.js +0 -0
  126. package/dist/tests/test-mini4k.js +0 -0
  127. package/dist/tests/tests/test-i18n.js +588 -0
  128. package/dist/tests/types.test.js +1 -1
  129. package/dist/tests/utils/ErrorHandler.test.js +144 -0
  130. package/dist/vitest.config.js +29 -0
  131. package/package.json +31 -27
  132. package/dist/src/cli-language.js +0 -78
  133. package/dist/src/client.d.ts +0 -6
  134. package/dist/src/client.js.map +0 -1
  135. package/dist/src/index.d.ts +0 -15
  136. package/dist/src/index.js.map +0 -1
  137. package/dist/src/lib/BrowserFetcher.d.ts +0 -175
  138. package/dist/src/lib/BrowserFetcher.js.map +0 -1
  139. package/dist/src/lib/NodeFetcher.d.ts +0 -92
  140. package/dist/src/lib/NodeFetcher.js.map +0 -1
  141. package/dist/src/lib/example.js +0 -93
  142. package/dist/src/lib/i18n/index.js.map +0 -1
  143. package/dist/src/lib/i18n/locales/en/browser.d.ts +0 -61
  144. package/dist/src/lib/i18n/locales/en/browser.js.map +0 -1
  145. package/dist/src/lib/i18n/locales/en/client.d.ts +0 -39
  146. package/dist/src/lib/i18n/locales/en/client.js.map +0 -1
  147. package/dist/src/lib/i18n/locales/en/errors.d.ts +0 -17
  148. package/dist/src/lib/i18n/locales/en/errors.js.map +0 -1
  149. package/dist/src/lib/i18n/locales/en/fetcher.d.ts +0 -66
  150. package/dist/src/lib/i18n/locales/en/fetcher.js.map +0 -1
  151. package/dist/src/lib/i18n/locales/en/index.d.ts +0 -344
  152. package/dist/src/lib/i18n/locales/en/index.js.map +0 -1
  153. package/dist/src/lib/i18n/locales/en/node.d.ts +0 -41
  154. package/dist/src/lib/i18n/locales/en/node.js.map +0 -1
  155. package/dist/src/lib/i18n/locales/en/prompts.d.ts +0 -65
  156. package/dist/src/lib/i18n/locales/en/prompts.js.map +0 -1
  157. package/dist/src/lib/i18n/locales/en/resources.d.ts +0 -41
  158. package/dist/src/lib/i18n/locales/en/resources.js.map +0 -1
  159. package/dist/src/lib/i18n/locales/en/server.d.ts +0 -30
  160. package/dist/src/lib/i18n/locales/en/server.js.map +0 -1
  161. package/dist/src/lib/i18n/locales/en/tools.d.ts +0 -24
  162. package/dist/src/lib/i18n/locales/en/tools.js.map +0 -1
  163. package/dist/src/lib/i18n/locales/zh/browser.d.ts +0 -61
  164. package/dist/src/lib/i18n/locales/zh/browser.js.map +0 -1
  165. package/dist/src/lib/i18n/locales/zh/client.d.ts +0 -38
  166. package/dist/src/lib/i18n/locales/zh/client.js.map +0 -1
  167. package/dist/src/lib/i18n/locales/zh/errors.d.ts +0 -17
  168. package/dist/src/lib/i18n/locales/zh/errors.js.map +0 -1
  169. package/dist/src/lib/i18n/locales/zh/fetcher.d.ts +0 -66
  170. package/dist/src/lib/i18n/locales/zh/fetcher.js.map +0 -1
  171. package/dist/src/lib/i18n/locales/zh/index.d.ts +0 -343
  172. package/dist/src/lib/i18n/locales/zh/index.js.map +0 -1
  173. package/dist/src/lib/i18n/locales/zh/node.d.ts +0 -41
  174. package/dist/src/lib/i18n/locales/zh/node.js.map +0 -1
  175. package/dist/src/lib/i18n/locales/zh/prompts.d.ts +0 -65
  176. package/dist/src/lib/i18n/locales/zh/prompts.js.map +0 -1
  177. package/dist/src/lib/i18n/locales/zh/resources.d.ts +0 -41
  178. package/dist/src/lib/i18n/locales/zh/resources.js.map +0 -1
  179. package/dist/src/lib/i18n/locales/zh/server.d.ts +0 -30
  180. package/dist/src/lib/i18n/locales/zh/server.js.map +0 -1
  181. package/dist/src/lib/i18n/locales/zh/tools.d.ts +0 -24
  182. package/dist/src/lib/i18n/locales/zh/tools.js.map +0 -1
  183. package/dist/src/lib/i18n/logger.d.ts +0 -38
  184. package/dist/src/lib/i18n/logger.js.map +0 -1
  185. package/dist/src/lib/logger.d.ts +0 -32
  186. package/dist/src/lib/logger.js.map +0 -1
  187. package/dist/src/lib/server/browser.d.ts +0 -34
  188. package/dist/src/lib/server/browser.js.map +0 -1
  189. package/dist/src/lib/server/fetcher.d.ts +0 -20
  190. package/dist/src/lib/server/fetcher.js.map +0 -1
  191. package/dist/src/lib/server/index.d.ts +0 -10
  192. package/dist/src/lib/server/index.js.map +0 -1
  193. package/dist/src/lib/server/logger.js +0 -23
  194. package/dist/src/lib/server/prompts.d.ts +0 -11
  195. package/dist/src/lib/server/prompts.js.map +0 -1
  196. package/dist/src/lib/server/resources.d.ts +0 -11
  197. package/dist/src/lib/server/resources.js.map +0 -1
  198. package/dist/src/lib/server/tools.d.ts +0 -12
  199. package/dist/src/lib/server/tools.js.map +0 -1
  200. package/dist/src/lib/server/types.d.ts +0 -71
  201. package/dist/src/lib/server/types.js.map +0 -1
  202. package/dist/src/lib/types.d.ts +0 -119
  203. package/dist/src/lib/types.js.map +0 -1
  204. package/dist/src/mcp-server.d.ts +0 -6
  205. package/dist/src/mcp-server.js.map +0 -1
  206. package/dist/tests/jest.setup.d.ts +0 -6
  207. package/dist/tests/jest.setup.js +0 -30
  208. package/dist/tests/jest.setup.js.map +0 -1
  209. package/dist/tests/test-direct-client.d.ts +0 -6
  210. package/dist/tests/test-direct-client.js.map +0 -1
  211. package/dist/tests/test-mcp-methods.d.ts +0 -6
  212. package/dist/tests/test-mcp-methods.js.map +0 -1
  213. package/dist/tests/test-mcp.d.ts +0 -6
  214. package/dist/tests/test-mcp.js.map +0 -1
  215. package/dist/tests/test-mini4k.d.ts +0 -6
  216. 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 = jest.fn();
14
- jest.mock('../src/lib/logger.js', () => ({
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
- jest.mock('@modelcontextprotocol/sdk/client/index.js', () => ({
25
- Client: jest.fn().mockImplementation(() => ({
26
- connect: jest.fn().mockResolvedValue(undefined),
27
- callTool: jest.fn(),
28
- close: jest.fn().mockResolvedValue(undefined)
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
- jest.mock('@modelcontextprotocol/sdk/client/stdio.js', () => ({
32
- StdioClientTransport: jest.fn().mockImplementation(() => ({}))
32
+ vi.mock('@modelcontextprotocol/sdk/client/stdio.js', () => ({
33
+ StdioClientTransport: vi.fn().mockImplementation(() => ({}))
33
34
  }));
34
- jest.mock('child_process', () => ({
35
- execSync: jest.fn()
35
+ vi.mock('child_process', () => ({
36
+ execSync: vi.fn()
36
37
  }));
37
38
  // 模拟 process.exit
38
- jest.spyOn(process, 'exit').mockImplementation((code) => {
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: jest.fn((response, debug = false) => {
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: jest.fn(async (params) => {
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: jest.fn(() => {
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
- jest.mock('../src/client.js', () => mockClientModule, { virtual: true });
111
+ vi.mock('../src/client.ts', () => mockClientModule);
111
112
  describe('client.ts 测试', () => {
112
113
  // 在每个测试前重置模拟
113
114
  beforeEach(() => {
114
- jest.resetModules();
115
- jest.clearAllMocks();
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 = jest.fn().mockResolvedValue(mockResult);
149
+ const mockCallTool = vi.fn().mockResolvedValue(mockResult);
167
150
  Client.mockImplementation(() => ({
168
- connect: jest.fn().mockResolvedValue(undefined),
151
+ connect: vi.fn().mockResolvedValue(undefined),
169
152
  callTool: mockCallTool,
170
- close: jest.fn().mockResolvedValue(undefined)
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 = jest.fn().mockResolvedValue(undefined);
175
+ const mockClose = vi.fn().mockResolvedValue(undefined);
193
176
  Client.mockImplementation(() => ({
194
- connect: jest.fn().mockResolvedValue(undefined),
195
- callTool: jest.fn().mockRejectedValue(new Error('Test error')),
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
- // 保存原始的 process.argv
210
- const testArgv = process.argv;
211
- try {
212
- // 设置参数不足
213
- process.argv = ['node', 'client.js'];
214
- // 重置 mockLog 函数
215
- mockLog.mockClear();
216
- // 调用 main 函数
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
- // 保存原始的 process.argv
231
- const testArgv = process.argv;
232
- try {
233
- // 设置无效的 JSON
234
- process.argv = ['node', 'client.js', 'fetch_html', 'invalid-json'];
235
- // 重置 mockLog 函数
236
- mockLog.mockClear();
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
+ });