1
+ // npx/纯净产物环境模拟测试
2
+ import { test , expect } from 'vitest' ;
3
+ import fs from 'fs' ;
4
+ import os from 'os' ;
5
+ import path from 'path' ;
6
+ import { execSync , spawnSync } from 'child_process' ;
7
+ import { fileURLToPath } from 'url' ;
8
+ import { dirname } from 'path' ;
9
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js' ;
10
+ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js' ;
11
+
12
+ const __filename = fileURLToPath ( import . meta. url ) ;
13
+ const __dirname = dirname ( __filename ) ;
14
+
15
+ // Helper function to wait for delay
16
+ const delay = ( ms ) => new Promise ( resolve => setTimeout ( resolve , ms ) ) ;
17
+
18
+ // MCP 连接测试函数
19
+ async function testMcpConnection ( cliPath ) {
20
+ let transport = null ;
21
+ let client = null ;
22
+
23
+ try {
24
+ console . log ( '📡 创建 MCP 客户端...' ) ;
25
+
26
+ // Create client
27
+ client = new Client ( {
28
+ name : "test-client-npx" ,
29
+ version : "1.0.0" ,
30
+ } , {
31
+ capabilities : { }
32
+ } ) ;
33
+
34
+ // Create stdio transport that spawns the server
35
+ transport = new StdioClientTransport ( {
36
+ command : 'node' ,
37
+ args : [ cliPath ] ,
38
+ env : { ...process . env }
39
+ } ) ;
40
+
41
+ // Connect client to server
42
+ console . log ( '🔗 连接到 MCP 服务器...' ) ;
43
+ await client . connect ( transport ) ;
44
+
45
+ // Wait for connection to establish
46
+ await delay ( 3000 ) ;
47
+
48
+ console . log ( '🔍 测试服务器功能...' ) ;
49
+
50
+ // List tools (this should work since we declared tools capability)
51
+ const toolsResult = await client . listTools ( ) ;
52
+ expect ( toolsResult . tools ) . toBeDefined ( ) ;
53
+ expect ( Array . isArray ( toolsResult . tools ) ) . toBe ( true ) ;
54
+ expect ( toolsResult . tools . length ) . toBeGreaterThan ( 0 ) ;
55
+
56
+ console . log ( `✅ 服务器暴露了 ${ toolsResult . tools . length } 个工具` ) ;
57
+
58
+ // Test a simple tool call (searchKnowledgeBase should always be available)
59
+ const knowledgeTool = toolsResult . tools . find ( t => t . name === 'searchKnowledgeBase' ) ;
60
+ if ( knowledgeTool ) {
61
+ console . log ( '🔍 测试 searchKnowledgeBase 工具...' ) ;
62
+
63
+ const knowledgeResult = await client . callTool ( {
64
+ name : 'searchKnowledgeBase' ,
65
+ arguments : {
66
+ id : 'cloudbase' , // 知识库范围
67
+ content : 'test' , // 检索内容
68
+ limit : 1 // 返回结果数量
69
+ }
70
+ } ) ;
71
+
72
+ expect ( knowledgeResult ) . toBeDefined ( ) ;
73
+ expect ( knowledgeResult . content ) . toBeDefined ( ) ;
74
+ expect ( Array . isArray ( knowledgeResult . content ) ) . toBe ( true ) ;
75
+
76
+ console . log ( '✅ searchKnowledgeBase 工具执行成功' ) ;
77
+ } else {
78
+ console . log ( '⚠️ searchKnowledgeBase 工具未找到,跳过测试' ) ;
79
+ }
80
+
81
+ console . log ( '✅ MCP 连接测试通过' ) ;
82
+
83
+ } catch ( error ) {
84
+ console . error ( '❌ MCP 连接测试失败:' , error ) ;
85
+ throw error ;
86
+ } finally {
87
+ // Clean up
88
+ if ( client ) {
89
+ try {
90
+ await client . close ( ) ;
91
+ console . log ( '✅ 客户端连接已关闭' ) ;
92
+ } catch ( e ) {
93
+ console . warn ( '⚠️ 关闭客户端连接时出错:' , e . message ) ;
94
+ }
95
+ }
96
+ if ( transport ) {
97
+ try {
98
+ await transport . close ( ) ;
99
+ console . log ( '✅ 传输连接已关闭' ) ;
100
+ } catch ( e ) {
101
+ console . warn ( '⚠️ 关闭传输连接时出错:' , e . message ) ;
102
+ }
103
+ }
104
+ }
105
+ }
106
+
107
+ test ( 'npx/纯净产物环境模拟测试' , async ( ) => {
108
+ const tmpDir = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , 'mcp-npx-test-' ) ) ;
109
+ let tarballPath = '' ;
110
+ let pkgDir = '' ;
111
+
112
+ try {
113
+ console . log ( '🔍 开始 npx 环境模拟测试...' ) ;
114
+
115
+ // 1. 打包
116
+ console . log ( '📦 执行 npm pack...' ) ;
117
+ tarballPath = execSync ( 'npm pack' , {
118
+ encoding : 'utf-8' ,
119
+ cwd : path . join ( __dirname , '../mcp' )
120
+ } )
121
+ . split ( '\n' )
122
+ . find ( line => line . endsWith ( '.tgz' ) )
123
+ . trim ( ) ;
124
+
125
+ console . log ( '📦 打包文件:' , tarballPath ) ;
126
+
127
+ // 2. 解包
128
+ console . log ( '📂 解包到临时目录...' ) ;
129
+ execSync ( `tar -xzf ${ tarballPath } -C ${ tmpDir } ` ) ;
130
+ pkgDir = path . join ( tmpDir , 'package' ) ;
131
+
132
+ console . log ( '📂 解包目录:' , pkgDir ) ;
133
+
134
+ // 3. 安装依赖(只安装 dependencies)
135
+ console . log ( '📥 安装生产依赖...' ) ;
136
+ execSync ( 'npm install --production' , {
137
+ cwd : pkgDir ,
138
+ stdio : 'inherit'
139
+ } ) ;
140
+
141
+ // 4. 运行 CLI 基础测试
142
+ console . log ( '🚀 测试 CLI 启动...' ) ;
143
+ const cliPath = path . join ( pkgDir , 'dist' , 'cli.cjs' ) ;
144
+ const result = spawnSync ( 'node' , [ cliPath , '--help' ] , {
145
+ encoding : 'utf-8' ,
146
+ timeout : 30000 // 30秒超时
147
+ } ) ;
148
+
149
+ // 5. 检查基础输出
150
+ console . log ( '🔍 检查基础运行结果...' ) ;
151
+ console . log ( '退出码:' , result . status ) ;
152
+ console . log ( '标准输出长度:' , result . stdout ?. length || 0 ) ;
153
+ console . log ( '错误输出长度:' , result . stderr ?. length || 0 ) ;
154
+
155
+ // 检查是否有依赖缺失错误
156
+ expect ( result . stderr ) . not . toMatch ( / M O D U L E _ N O T _ F O U N D | C a n n o t f i n d m o d u l e / ) ;
157
+ expect ( result . status ) . toBe ( 0 ) ;
158
+
159
+ // 可选:检查 stdout 是否包含预期内容
160
+ if ( result . stdout ) {
161
+ console . log ( '✅ CLI 输出正常' ) ;
162
+ }
163
+
164
+ // 6. MCP 连接测试
165
+ console . log ( '🔗 开始 MCP 连接测试...' ) ;
166
+ await testMcpConnection ( cliPath ) ;
167
+
168
+ // 7. 环境信息查询测试
169
+ console . log ( '🔍 开始环境信息查询测试...' ) ;
170
+ await testEnvironmentInfo ( cliPath ) ;
171
+
172
+ console . log ( '✅ npx 环境模拟测试通过' ) ;
173
+
174
+ } catch ( error ) {
175
+ console . error ( '❌ npx 环境模拟测试失败:' , error ) ;
176
+ throw error ;
177
+ } finally {
178
+ // 清理临时目录和 tar 包
179
+ console . log ( '🧹 清理临时文件...' ) ;
180
+ try {
181
+ fs . rmSync ( tmpDir , { recursive : true , force : true } ) ;
182
+ console . log ( '✅ 临时目录清理完成' ) ;
183
+ } catch ( e ) {
184
+ console . warn ( '⚠️ 临时目录清理失败:' , e . message ) ;
185
+ }
186
+
187
+ try {
188
+ fs . unlinkSync ( tarballPath ) ;
189
+ console . log ( '✅ tar 包清理完成' ) ;
190
+ } catch ( e ) {
191
+ console . warn ( '⚠️ tar 包清理失败:' , e . message ) ;
192
+ }
193
+ }
194
+ } , 120000 ) ; // 增加到 120 秒
195
+
196
+ // 环境信息查询测试函数
197
+ async function testEnvironmentInfo ( cliPath ) {
198
+ let transport = null ;
199
+ let client = null ;
200
+
201
+ try {
202
+ console . log ( '📡 创建环境信息查询客户端...' ) ;
203
+
204
+ // Create client
205
+ client = new Client ( {
206
+ name : "test-client-env-info" ,
207
+ version : "1.0.0" ,
208
+ } , {
209
+ capabilities : { }
210
+ } ) ;
211
+
212
+ // Create stdio transport that spawns the server
213
+ transport = new StdioClientTransport ( {
214
+ command : 'node' ,
215
+ args : [ cliPath ] ,
216
+ env : { ...process . env }
217
+ } ) ;
218
+
219
+ // Connect client to server
220
+ console . log ( '🔗 连接到 MCP 服务器...' ) ;
221
+ await client . connect ( transport ) ;
222
+
223
+ // Wait for connection to establish
224
+ await delay ( 3000 ) ;
225
+
226
+ console . log ( '🔍 查询环境信息...' ) ;
227
+
228
+ // List tools to find environment-related tools
229
+ const toolsResult = await client . listTools ( ) ;
230
+ const envTools = toolsResult . tools . filter ( t =>
231
+ t . name . includes ( 'env' ) || t . name . includes ( 'login' ) || t . name . includes ( 'info' )
232
+ ) ;
233
+
234
+ console . log ( `📋 找到 ${ envTools . length } 个环境相关工具:` , envTools . map ( t => t . name ) ) ;
235
+
236
+ // Test login tool if available (this is a common environment tool)
237
+ const loginTool = toolsResult . tools . find ( t => t . name === 'login' ) ;
238
+ if ( loginTool ) {
239
+ console . log ( '🔐 测试 login 工具...' ) ;
240
+
241
+ try {
242
+ const loginResult = await client . callTool ( {
243
+ name : 'login' ,
244
+ arguments : {
245
+ secretId : 'test-secret-id' ,
246
+ secretKey : 'test-secret-key' ,
247
+ envId : 'test-env-id'
248
+ }
249
+ } ) ;
250
+
251
+ expect ( loginResult ) . toBeDefined ( ) ;
252
+ expect ( loginResult . content ) . toBeDefined ( ) ;
253
+ expect ( Array . isArray ( loginResult . content ) ) . toBe ( true ) ;
254
+
255
+ console . log ( '✅ login 工具执行成功' ) ;
256
+ console . log ( 'Login 结果:' , loginResult . content [ 0 ] ?. text ?. substring ( 0 , 200 ) + '...' ) ;
257
+ } catch ( loginError ) {
258
+ // Login might fail with test credentials, which is expected
259
+ console . log ( '⚠️ login 工具执行失败(使用测试凭据,这是预期的):' , loginError . message ) ;
260
+ }
261
+ } else {
262
+ console . log ( '⚠️ login 工具未找到,跳过测试' ) ;
263
+ }
264
+
265
+ // Test getEnvironmentInfo tool if available
266
+ const envInfoTool = toolsResult . tools . find ( t => t . name === 'getEnvironmentInfo' ) ;
267
+ if ( envInfoTool ) {
268
+ console . log ( '🌍 测试 getEnvironmentInfo 工具...' ) ;
269
+
270
+ try {
271
+ const envInfoResult = await client . callTool ( {
272
+ name : 'getEnvironmentInfo' ,
273
+ arguments : { }
274
+ } ) ;
275
+
276
+ expect ( envInfoResult ) . toBeDefined ( ) ;
277
+ expect ( envInfoResult . content ) . toBeDefined ( ) ;
278
+ expect ( Array . isArray ( envInfoResult . content ) ) . toBe ( true ) ;
279
+
280
+ console . log ( '✅ getEnvironmentInfo 工具执行成功' ) ;
281
+ console . log ( '环境信息:' , envInfoResult . content [ 0 ] ?. text ?. substring ( 0 , 200 ) + '...' ) ;
282
+ } catch ( envInfoError ) {
283
+ console . log ( '⚠️ getEnvironmentInfo 工具执行失败(可能需要认证):' , envInfoError . message ) ;
284
+ }
285
+ } else {
286
+ console . log ( '⚠️ getEnvironmentInfo 工具未找到,跳过测试' ) ;
287
+ }
288
+
289
+ // Test listEnvironments tool if available
290
+ const listEnvsTool = toolsResult . tools . find ( t => t . name === 'listEnvironments' ) ;
291
+ if ( listEnvsTool ) {
292
+ console . log ( '📋 测试 listEnvironments 工具...' ) ;
293
+
294
+ try {
295
+ const listEnvsResult = await client . callTool ( {
296
+ name : 'listEnvironments' ,
297
+ arguments : { }
298
+ } ) ;
299
+
300
+ expect ( listEnvsResult ) . toBeDefined ( ) ;
301
+ expect ( listEnvsResult . content ) . toBeDefined ( ) ;
302
+ expect ( Array . isArray ( listEnvsResult . content ) ) . toBe ( true ) ;
303
+
304
+ console . log ( '✅ listEnvironments 工具执行成功' ) ;
305
+ console . log ( '环境列表:' , listEnvsResult . content [ 0 ] ?. text ?. substring ( 0 , 200 ) + '...' ) ;
306
+ } catch ( listEnvsError ) {
307
+ console . log ( '⚠️ listEnvironments 工具执行失败(可能需要认证):' , listEnvsError . message ) ;
308
+ }
309
+ } else {
310
+ console . log ( '⚠️ listEnvironments 工具未找到,跳过测试' ) ;
311
+ }
312
+
313
+ console . log ( '✅ 环境信息查询测试通过' ) ;
314
+
315
+ } catch ( error ) {
316
+ console . error ( '❌ 环境信息查询测试失败:' , error ) ;
317
+ throw error ;
318
+ } finally {
319
+ // Clean up
320
+ if ( client ) {
321
+ try {
322
+ await client . close ( ) ;
323
+ console . log ( '✅ 环境信息查询客户端连接已关闭' ) ;
324
+ } catch ( e ) {
325
+ console . warn ( '⚠️ 关闭环境信息查询客户端连接时出错:' , e . message ) ;
326
+ }
327
+ }
328
+ if ( transport ) {
329
+ try {
330
+ await transport . close ( ) ;
331
+ console . log ( '✅ 环境信息查询传输连接已关闭' ) ;
332
+ } catch ( e ) {
333
+ console . warn ( '⚠️ 关闭环境信息查询传输连接时出错:' , e . message ) ;
334
+ }
335
+ }
336
+ }
337
+ }
0 commit comments