@becrafter/prompt-manager 0.1.17 → 0.1.20
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/env.example +1 -1
- package/package.json +19 -7
- package/packages/server/server.js +2 -1
- package/packages/server/services/TerminalService.js +247 -13
- package/packages/server/toolm/tool-sync.service.js +8 -0
- package/packages/server/utils/config.js +1 -1
- package/packages/web/css/{main.3b61356b384d2f11f47f.css → main.196f434e6a88cd448158.css} +10 -0
- package/packages/web/index.html +1 -1
- package/packages/web/{main.77c2c4b553ca3fac223b.js → main.b427a9e6f77a32a2f87f.js} +2 -2
- package/app/desktop/assets/app.1.png +0 -0
- package/app/desktop/assets/app.png +0 -0
- package/app/desktop/assets/icons/icon.icns +0 -0
- package/app/desktop/assets/icons/icon.ico +0 -0
- package/app/desktop/assets/icons/icon.png +0 -0
- package/app/desktop/assets/icons/tray.png +0 -0
- package/app/desktop/assets/templates/about.html +0 -147
- package/app/desktop/assets/tray.1.png +0 -0
- package/app/desktop/assets/tray.png +0 -0
- package/app/desktop/docs/ASSETS_PLANNING.md +0 -351
- package/app/desktop/docs/REFACTORING_SUMMARY.md +0 -205
- package/app/desktop/main.js +0 -340
- package/app/desktop/package-lock.json +0 -6912
- package/app/desktop/package.json +0 -119
- package/app/desktop/preload.js +0 -7
- package/app/desktop/src/core/error-handler.js +0 -108
- package/app/desktop/src/core/event-emitter.js +0 -84
- package/app/desktop/src/core/logger.js +0 -130
- package/app/desktop/src/core/state-manager.js +0 -125
- package/app/desktop/src/services/module-loader.js +0 -330
- package/app/desktop/src/services/runtime-manager.js +0 -398
- package/app/desktop/src/services/service-manager.js +0 -210
- package/app/desktop/src/services/update-manager.js +0 -267
- package/app/desktop/src/ui/about-dialog-manager.js +0 -208
- package/app/desktop/src/ui/admin-window-manager.js +0 -757
- package/app/desktop/src/ui/splash-manager.js +0 -253
- package/app/desktop/src/ui/tray-manager.js +0 -186
- package/app/desktop/src/utils/icon-manager.js +0 -133
- package/app/desktop/src/utils/path-utils.js +0 -58
- package/app/desktop/src/utils/resource-paths.js +0 -49
- package/app/desktop/src/utils/resource-sync.js +0 -260
- package/app/desktop/src/utils/runtime-sync.js +0 -241
- package/app/desktop/src/utils/self-check.js +0 -288
- package/app/desktop/src/utils/template-renderer.js +0 -284
- package/app/desktop/src/utils/version-utils.js +0 -59
- package/packages/server/.eslintrc.js +0 -70
- package/packages/server/.husky/pre-commit +0 -8
- package/packages/server/.husky/pre-push +0 -8
- package/packages/server/.prettierrc +0 -14
- package/packages/server/dev-server.js +0 -90
- package/packages/server/jsdoc.conf.json +0 -39
- package/packages/server/package.json +0 -85
- package/packages/server/playwright.config.js +0 -62
- package/packages/server/scripts/generate-docs.js +0 -300
- package/packages/server/tests/e2e/terminal-e2e.test.js +0 -315
- package/packages/server/tests/integration/terminal-websocket.test.js +0 -372
- package/packages/server/tests/integration/tools.test.js +0 -264
- package/packages/server/tests/setup.js +0 -45
- package/packages/server/tests/unit/TerminalService.test.js +0 -410
- package/packages/server/tests/unit/WebSocketService.test.js +0 -403
- package/packages/server/tests/unit/core.test.js +0 -94
- package/packages/server/typedoc.json +0 -52
- package/packages/server/vitest.config.js +0 -74
- /package/packages/web/{main.77c2c4b553ca3fac223b.js.LICENSE.txt → main.b427a9e6f77a32a2f87f.js.LICENSE.txt} +0 -0
|
@@ -1,315 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 终端E2E测试
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { test, expect } from '@playwright/test';
|
|
6
|
-
|
|
7
|
-
test.describe('终端功能E2E测试', () => {
|
|
8
|
-
test.beforeEach(async ({ page }) => {
|
|
9
|
-
// 导航到管理员界面
|
|
10
|
-
await page.goto('/admin/');
|
|
11
|
-
|
|
12
|
-
// 等待页面加载完成
|
|
13
|
-
await page.waitForLoadState('networkidle');
|
|
14
|
-
|
|
15
|
-
// 点击终端导航
|
|
16
|
-
await page.click('[data-nav="terminal"]');
|
|
17
|
-
|
|
18
|
-
// 等待终端区域显示
|
|
19
|
-
await page.waitForSelector('#terminalArea', { state: 'visible' });
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
test('应该显示终端界面', async ({ page }) => {
|
|
23
|
-
// 验证终端区域存在
|
|
24
|
-
await expect(page.locator('#terminalArea')).toBeVisible();
|
|
25
|
-
|
|
26
|
-
// 验证终端标题
|
|
27
|
-
await expect(page.locator('.terminal-title')).toContainText('终端');
|
|
28
|
-
|
|
29
|
-
// 验证终端内容区域
|
|
30
|
-
await expect(page.locator('.terminal-content')).toBeVisible();
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test('应该建立WebSocket连接', async ({ page }) => {
|
|
34
|
-
// 等待WebSocket连接建立
|
|
35
|
-
await page.waitForSelector('.status-indicator.connected', { timeout: 10000 });
|
|
36
|
-
|
|
37
|
-
// 验证连接状态显示
|
|
38
|
-
await expect(page.locator('.status-text')).toContainText('已连接');
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
test('应该支持基本命令执行', async ({ page }) => {
|
|
42
|
-
// 等待终端初始化
|
|
43
|
-
await page.waitForSelector('.xterm-container', { state: 'visible', timeout: 10000 });
|
|
44
|
-
|
|
45
|
-
// 模拟输入命令
|
|
46
|
-
await page.keyboard.type('echo "Hello, E2E Test!"');
|
|
47
|
-
await page.keyboard.press('Enter');
|
|
48
|
-
|
|
49
|
-
// 等待命令执行结果
|
|
50
|
-
await page.waitForTimeout(2000);
|
|
51
|
-
|
|
52
|
-
// 验证命令输出(这里需要根据实际终端实现调整)
|
|
53
|
-
// 由于使用xterm.js,我们需要检查终端内容
|
|
54
|
-
const terminalContent = await page.locator('.xterm-screen').textContent();
|
|
55
|
-
expect(terminalContent).toContain('Hello, E2E Test!');
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test('应该支持多个命令执行', async ({ page }) => {
|
|
59
|
-
await page.waitForSelector('.xterm-container', { state: 'visible', timeout: 10000 });
|
|
60
|
-
|
|
61
|
-
// 执行多个命令
|
|
62
|
-
const commands = [
|
|
63
|
-
'pwd',
|
|
64
|
-
'ls -la',
|
|
65
|
-
'whoami'
|
|
66
|
-
];
|
|
67
|
-
|
|
68
|
-
for (const command of commands) {
|
|
69
|
-
await page.keyboard.type(command);
|
|
70
|
-
await page.keyboard.press('Enter');
|
|
71
|
-
await page.waitForTimeout(1000);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// 验证所有命令都已执行
|
|
75
|
-
const terminalContent = await page.locator('.xterm-screen').textContent();
|
|
76
|
-
|
|
77
|
-
// 验证命令历史
|
|
78
|
-
for (const command of commands) {
|
|
79
|
-
expect(terminalContent).toContain(command);
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
test('应该支持终端大小调整', async ({ page }) => {
|
|
84
|
-
await page.waitForSelector('.xterm-container', { state: 'visible', timeout: 10000 });
|
|
85
|
-
|
|
86
|
-
// 获取初始终端大小
|
|
87
|
-
const initialTerminal = page.locator('.xterm-screen');
|
|
88
|
-
const initialBounds = await initialTerminal.boundingBox();
|
|
89
|
-
|
|
90
|
-
// 调整浏览器窗口大小
|
|
91
|
-
await page.setViewportSize({ width: 1200, height: 800 });
|
|
92
|
-
|
|
93
|
-
// 等待终端适应新大小
|
|
94
|
-
await page.waitForTimeout(1000);
|
|
95
|
-
|
|
96
|
-
// 验证终端已调整大小
|
|
97
|
-
const adjustedTerminal = page.locator('.xterm-screen');
|
|
98
|
-
const adjustedBounds = await adjustedTerminal.boundingBox();
|
|
99
|
-
|
|
100
|
-
// 终端应该适应新的窗口大小
|
|
101
|
-
expect(adjustedBounds.width).toBeGreaterThan(initialBounds.width);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
test('应该支持主题切换', async ({ page }) => {
|
|
105
|
-
await page.waitForSelector('.xterm-container', { state: 'visible', timeout: 10000 });
|
|
106
|
-
|
|
107
|
-
// 获取初始主题
|
|
108
|
-
const terminal = page.locator('.xterm');
|
|
109
|
-
const initialBackground = await terminal.evaluate(el =>
|
|
110
|
-
getComputedStyle(el).getPropertyValue('background-color')
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
// 点击主题切换按钮
|
|
114
|
-
await page.click('#themeBtn');
|
|
115
|
-
|
|
116
|
-
// 等待主题切换
|
|
117
|
-
await page.waitForTimeout(500);
|
|
118
|
-
|
|
119
|
-
// 验证主题已改变
|
|
120
|
-
const newBackground = await terminal.evaluate(el =>
|
|
121
|
-
getComputedStyle(el).getPropertyValue('background-color')
|
|
122
|
-
);
|
|
123
|
-
|
|
124
|
-
expect(newBackground).not.toBe(initialBackground);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
test('应该支持搜索功能', async ({ page }) => {
|
|
128
|
-
await page.waitForSelector('.xterm-container', { state: 'visible', timeout: 10000 });
|
|
129
|
-
|
|
130
|
-
// 输入一些文本
|
|
131
|
-
await page.keyboard.type('echo "search test"');
|
|
132
|
-
await page.keyboard.press('Enter');
|
|
133
|
-
await page.waitForTimeout(1000);
|
|
134
|
-
|
|
135
|
-
// 点击搜索按钮
|
|
136
|
-
await page.click('#searchBtn');
|
|
137
|
-
|
|
138
|
-
// 输入搜索词
|
|
139
|
-
const searchInput = await page.locator('.xterm-search input');
|
|
140
|
-
await searchInput.fill('search test');
|
|
141
|
-
|
|
142
|
-
// 验证搜索功能(这里需要根据实际搜索实现调整)
|
|
143
|
-
expect(searchInput).toHaveValue('search test');
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
test('应该支持清除功能', async ({ page }) => {
|
|
147
|
-
await page.waitForSelector('.xterm-container', { state: 'visible', timeout: 10000 });
|
|
148
|
-
|
|
149
|
-
// 输入一些内容
|
|
150
|
-
await page.keyboard.type('echo "clear test"');
|
|
151
|
-
await page.keyboard.press('Enter');
|
|
152
|
-
await page.waitForTimeout(1000);
|
|
153
|
-
|
|
154
|
-
// 获取清除前的内容
|
|
155
|
-
const contentBefore = await page.locator('.xterm-screen').textContent();
|
|
156
|
-
expect(contentBefore).toContain('clear test');
|
|
157
|
-
|
|
158
|
-
// 点击清除按钮
|
|
159
|
-
await page.click('#clearBtn');
|
|
160
|
-
|
|
161
|
-
// 等待清除完成
|
|
162
|
-
await page.waitForTimeout(500);
|
|
163
|
-
|
|
164
|
-
// 验证内容已清除(xterm.js清除后可能只保留提示符)
|
|
165
|
-
const contentAfter = await page.locator('.xterm-screen').textContent();
|
|
166
|
-
expect(contentAfter).not.toContain('clear test');
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
test('应该支持重连功能', async ({ page }) => {
|
|
170
|
-
await page.waitForSelector('.xterm-container', { state: 'visible', timeout: 10000 });
|
|
171
|
-
|
|
172
|
-
// 等待连接建立
|
|
173
|
-
await page.waitForSelector('.status-indicator.connected');
|
|
174
|
-
|
|
175
|
-
// 点击重连按钮
|
|
176
|
-
await page.click('#reconnectBtn');
|
|
177
|
-
|
|
178
|
-
// 等待重连完成
|
|
179
|
-
await page.waitForSelector('.status-indicator.connected', { timeout: 10000 });
|
|
180
|
-
|
|
181
|
-
// 验证连接状态
|
|
182
|
-
await expect(page.locator('.status-text')).toContainText('已连接');
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
test('应该处理连接断开', async ({ page }) => {
|
|
186
|
-
await page.waitForSelector('.xterm-container', { state: 'visible', timeout: 10000 });
|
|
187
|
-
|
|
188
|
-
// 模拟WebSocket断开(通过页面上下文)
|
|
189
|
-
await page.evaluate(() => {
|
|
190
|
-
// 查找WebSocket连接并关闭
|
|
191
|
-
const ws = window.terminalComponent?.websocket;
|
|
192
|
-
if (ws) {
|
|
193
|
-
ws.close();
|
|
194
|
-
}
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
// 等待断开状态
|
|
198
|
-
await page.waitForSelector('.status-indicator.disconnected', { timeout: 5000 });
|
|
199
|
-
|
|
200
|
-
// 验证断开状态显示
|
|
201
|
-
await expect(page.locator('.status-text')).toContainText('未连接');
|
|
202
|
-
|
|
203
|
-
// 等待自动重连
|
|
204
|
-
await page.waitForSelector('.status-indicator.connected', { timeout: 15000 });
|
|
205
|
-
|
|
206
|
-
// 验证重连成功
|
|
207
|
-
await expect(page.locator('.status-text')).toContainText('已连接');
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
test('应该支持键盘快捷键', async ({ page }) => {
|
|
211
|
-
await page.waitForSelector('.xterm-container', { state: 'visible', timeout: 10000 });
|
|
212
|
-
|
|
213
|
-
// 测试Ctrl+C中断
|
|
214
|
-
await page.keyboard.type('sleep 10');
|
|
215
|
-
await page.keyboard.press('Enter');
|
|
216
|
-
await page.waitForTimeout(500);
|
|
217
|
-
|
|
218
|
-
// 发送中断信号
|
|
219
|
-
await page.keyboard.press('Control+c');
|
|
220
|
-
await page.waitForTimeout(1000);
|
|
221
|
-
|
|
222
|
-
// 验证命令被中断(终端应该回到提示符状态)
|
|
223
|
-
// 这里需要根据实际终端实现来验证
|
|
224
|
-
|
|
225
|
-
// 测试Ctrl+V粘贴
|
|
226
|
-
await page.evaluate(() => {
|
|
227
|
-
navigator.clipboard.writeText('echo "paste test"');
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
await page.keyboard.press('Control+v');
|
|
231
|
-
await page.keyboard.press('Enter');
|
|
232
|
-
await page.waitForTimeout(1000);
|
|
233
|
-
|
|
234
|
-
// 验证粘贴内容被执行
|
|
235
|
-
const terminalContent = await page.locator('.xterm-screen').textContent();
|
|
236
|
-
expect(terminalContent).toContain('paste test');
|
|
237
|
-
});
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
test.describe('终端性能测试', () => {
|
|
241
|
-
test('应该在合理时间内建立连接', async ({ page }) => {
|
|
242
|
-
const startTime = Date.now();
|
|
243
|
-
|
|
244
|
-
await page.goto('/admin/');
|
|
245
|
-
await page.click('[data-nav="terminal"]');
|
|
246
|
-
await page.waitForSelector('.status-indicator.connected', { timeout: 10000 });
|
|
247
|
-
|
|
248
|
-
const connectionTime = Date.now() - startTime;
|
|
249
|
-
expect(connectionTime).toBeLessThan(5000); // 应该在5秒内连接
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
test('应该处理大量输出', async ({ page }) => {
|
|
253
|
-
await page.goto('/admin/');
|
|
254
|
-
await page.click('[data-nav="terminal"]');
|
|
255
|
-
await page.waitForSelector('.xterm-container', { state: 'visible', timeout: 10000 });
|
|
256
|
-
|
|
257
|
-
// 生成大量输出的命令
|
|
258
|
-
await page.keyboard.type('for i in {1..100}; do echo "Line $i"; done');
|
|
259
|
-
await page.keyboard.press('Enter');
|
|
260
|
-
|
|
261
|
-
// 等待命令执行完成
|
|
262
|
-
await page.waitForTimeout(5000);
|
|
263
|
-
|
|
264
|
-
// 验证终端仍然响应
|
|
265
|
-
await page.keyboard.type('echo "Still responsive"');
|
|
266
|
-
await page.keyboard.press('Enter');
|
|
267
|
-
await page.waitForTimeout(1000);
|
|
268
|
-
|
|
269
|
-
const terminalContent = await page.locator('.xterm-screen').textContent();
|
|
270
|
-
expect(terminalContent).toContain('Still responsive');
|
|
271
|
-
});
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
test.describe('跨浏览器兼容性', () => {
|
|
275
|
-
['chromium', 'firefox', 'webkit'].forEach(browserName => {
|
|
276
|
-
test(`${browserName}: 应该正常工作`, async ({ page, browserName: currentBrowser }) => {
|
|
277
|
-
test.skip(currentBrowser !== browserName, '跳过其他浏览器');
|
|
278
|
-
|
|
279
|
-
await page.goto('/admin/');
|
|
280
|
-
await page.click('[data-nav="terminal"]');
|
|
281
|
-
await page.waitForSelector('.xterm-container', { state: 'visible', timeout: 15000 });
|
|
282
|
-
|
|
283
|
-
// 基本功能测试
|
|
284
|
-
await page.keyboard.type('echo "Browser: $browserName"'.replace('$browserName', browserName));
|
|
285
|
-
await page.keyboard.press('Enter');
|
|
286
|
-
await page.waitForTimeout(2000);
|
|
287
|
-
|
|
288
|
-
const terminalContent = await page.locator('.xterm-screen').textContent();
|
|
289
|
-
expect(terminalContent).toContain(browserName);
|
|
290
|
-
});
|
|
291
|
-
});
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
test.describe('移动端适配', () => {
|
|
295
|
-
test('应该在移动设备上正常显示', async ({ page }) => {
|
|
296
|
-
// 设置移动设备视口
|
|
297
|
-
await page.setViewportSize({ width: 375, height: 667 });
|
|
298
|
-
|
|
299
|
-
await page.goto('/admin/');
|
|
300
|
-
await page.click('[data-nav="terminal"]');
|
|
301
|
-
await page.waitForSelector('.xterm-container', { state: 'visible', timeout: 15000 });
|
|
302
|
-
|
|
303
|
-
// 验证移动端样式
|
|
304
|
-
const toolbar = page.locator('.terminal-toolbar');
|
|
305
|
-
await expect(toolbar).toBeVisible();
|
|
306
|
-
|
|
307
|
-
// 验证终端仍然可用
|
|
308
|
-
await page.keyboard.type('echo "Mobile test"');
|
|
309
|
-
await page.keyboard.press('Enter');
|
|
310
|
-
await page.waitForTimeout(2000);
|
|
311
|
-
|
|
312
|
-
const terminalContent = await page.locator('.xterm-screen').textContent();
|
|
313
|
-
expect(terminalContent).toContain('Mobile test');
|
|
314
|
-
});
|
|
315
|
-
});
|
|
@@ -1,372 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 终端和WebSocket服务集成测试
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
6
|
-
import { WebSocket } from 'ws';
|
|
7
|
-
import { terminalService } from '../../services/TerminalService.js';
|
|
8
|
-
import { webSocketService } from '../../services/WebSocketService.js';
|
|
9
|
-
import { startServer, stopServer } from '../../server.js';
|
|
10
|
-
|
|
11
|
-
// Mock WebSocket客户端
|
|
12
|
-
vi.mock('ws', () => ({
|
|
13
|
-
WebSocket: vi.fn().mockImplementation(() => ({
|
|
14
|
-
readyState: 1,
|
|
15
|
-
send: vi.fn(),
|
|
16
|
-
close: vi.fn(),
|
|
17
|
-
on: vi.fn(),
|
|
18
|
-
addEventListener: vi.fn()
|
|
19
|
-
}))
|
|
20
|
-
}));
|
|
21
|
-
|
|
22
|
-
describe('Terminal and WebSocket Integration', () => {
|
|
23
|
-
let server;
|
|
24
|
-
let wsPort;
|
|
25
|
-
let mockWebSocketClient;
|
|
26
|
-
|
|
27
|
-
beforeEach(async () => {
|
|
28
|
-
vi.clearAllMocks();
|
|
29
|
-
|
|
30
|
-
// 启动测试服务器
|
|
31
|
-
server = await startServer({
|
|
32
|
-
configOverrides: {
|
|
33
|
-
port: 0, // 使用随机端口
|
|
34
|
-
adminEnable: true
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
// 获取服务器端口
|
|
39
|
-
const address = server.address();
|
|
40
|
-
wsPort = 8081; // WebSocket固定端口
|
|
41
|
-
|
|
42
|
-
// 创建Mock WebSocket客户端
|
|
43
|
-
mockWebSocketClient = new WebSocket();
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
afterEach(async () => {
|
|
47
|
-
// 清理终端会话
|
|
48
|
-
await terminalService.shutdown();
|
|
49
|
-
|
|
50
|
-
// 停止WebSocket服务
|
|
51
|
-
await webSocketService.stop();
|
|
52
|
-
|
|
53
|
-
// 停止服务器
|
|
54
|
-
if (server) {
|
|
55
|
-
await stopServer();
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
describe('完整终端会话流程', () => {
|
|
60
|
-
it('应该完成完整的终端会话生命周期', async () => {
|
|
61
|
-
// 启动WebSocket服务
|
|
62
|
-
await webSocketService.start();
|
|
63
|
-
|
|
64
|
-
// 模拟WebSocket连接
|
|
65
|
-
const mockConnection = {
|
|
66
|
-
clientId: 'test-client-1',
|
|
67
|
-
sessionId: null,
|
|
68
|
-
ws: {
|
|
69
|
-
readyState: 1,
|
|
70
|
-
send: vi.fn(),
|
|
71
|
-
close: vi.fn(),
|
|
72
|
-
on: vi.fn()
|
|
73
|
-
},
|
|
74
|
-
send: vi.fn(),
|
|
75
|
-
sendError: vi.fn()
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
// 添加连接到服务
|
|
79
|
-
webSocketService.connections.set(mockConnection.clientId, mockConnection);
|
|
80
|
-
|
|
81
|
-
// 1. 创建终端会话
|
|
82
|
-
const createMessage = {
|
|
83
|
-
type: 'terminal.create',
|
|
84
|
-
sessionId: 'integration-test-session',
|
|
85
|
-
size: { cols: 80, rows: 24 },
|
|
86
|
-
workingDirectory: process.cwd()
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
// 处理创建会话消息
|
|
90
|
-
await mockConnection.handleMessage?.(JSON.stringify(createMessage));
|
|
91
|
-
|
|
92
|
-
// 验证会话创建
|
|
93
|
-
expect(terminalService.hasSession('integration-test-session')).toBe(true);
|
|
94
|
-
const session = terminalService.getSession('integration-test-session');
|
|
95
|
-
expect(session).toBeDefined();
|
|
96
|
-
expect(session.size).toEqual({ cols: 80, rows: 24 });
|
|
97
|
-
|
|
98
|
-
// 2. 发送命令到终端
|
|
99
|
-
const dataMessage = {
|
|
100
|
-
type: 'terminal.data',
|
|
101
|
-
data: 'echo "Hello, World!"\n'
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
await mockConnection.handleMessage?.(JSON.stringify(dataMessage));
|
|
105
|
-
|
|
106
|
-
// 验证数据发送
|
|
107
|
-
expect(session.write).toHaveBeenCalledWith('echo "Hello, World!"\n');
|
|
108
|
-
|
|
109
|
-
// 3. 调整终端大小
|
|
110
|
-
const resizeMessage = {
|
|
111
|
-
type: 'terminal.resize',
|
|
112
|
-
cols: 120,
|
|
113
|
-
rows: 30
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
await mockConnection.handleMessage?.(JSON.stringify(resizeMessage));
|
|
117
|
-
|
|
118
|
-
// 验证大小调整
|
|
119
|
-
expect(session.resize).toHaveBeenCalledWith(120, 30);
|
|
120
|
-
|
|
121
|
-
// 4. 关闭终端会话
|
|
122
|
-
const closeMessage = {
|
|
123
|
-
type: 'terminal.close'
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
await mockConnection.handleMessage?.(JSON.stringify(closeMessage));
|
|
127
|
-
|
|
128
|
-
// 验证会话关闭
|
|
129
|
-
expect(terminalService.hasSession('integration-test-session')).toBe(false);
|
|
130
|
-
}, 15000);
|
|
131
|
-
|
|
132
|
-
it('应该处理多个并发终端会话', async () => {
|
|
133
|
-
await webSocketService.start();
|
|
134
|
-
|
|
135
|
-
// 创建多个客户端连接
|
|
136
|
-
const clients = [];
|
|
137
|
-
for (let i = 0; i < 3; i++) {
|
|
138
|
-
const client = {
|
|
139
|
-
clientId: `test-client-${i}`,
|
|
140
|
-
sessionId: null,
|
|
141
|
-
ws: {
|
|
142
|
-
readyState: 1,
|
|
143
|
-
send: vi.fn(),
|
|
144
|
-
close: vi.fn(),
|
|
145
|
-
on: vi.fn()
|
|
146
|
-
},
|
|
147
|
-
send: vi.fn(),
|
|
148
|
-
sendError: vi.fn()
|
|
149
|
-
};
|
|
150
|
-
clients.push(client);
|
|
151
|
-
webSocketService.connections.set(client.clientId, client);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// 为每个客户端创建会话
|
|
155
|
-
const sessions = [];
|
|
156
|
-
for (let i = 0; i < clients.length; i++) {
|
|
157
|
-
const createMessage = {
|
|
158
|
-
type: 'terminal.create',
|
|
159
|
-
sessionId: `session-${i}`,
|
|
160
|
-
size: { cols: 80, rows: 24 }
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
await clients[i].handleMessage?.(JSON.stringify(createMessage));
|
|
164
|
-
sessions.push(`session-${i}`);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// 验证所有会话都已创建
|
|
168
|
-
expect(terminalService.getAllSessions()).toHaveLength(3);
|
|
169
|
-
|
|
170
|
-
// 并发发送命令
|
|
171
|
-
const commands = ['echo "test1"', 'echo "test2"', 'echo "test3"'];
|
|
172
|
-
const promises = clients.map((client, index) => {
|
|
173
|
-
const dataMessage = {
|
|
174
|
-
type: 'terminal.data',
|
|
175
|
-
data: `${commands[index]}\n`
|
|
176
|
-
};
|
|
177
|
-
return client.handleMessage?.(JSON.stringify(dataMessage));
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
await Promise.all(promises);
|
|
181
|
-
|
|
182
|
-
// 验证所有命令都已发送
|
|
183
|
-
for (const sessionId of sessions) {
|
|
184
|
-
const session = terminalService.getSession(sessionId);
|
|
185
|
-
expect(session.write).toHaveBeenCalled();
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// 清理所有会话
|
|
189
|
-
for (const client of clients) {
|
|
190
|
-
const closeMessage = { type: 'terminal.close' };
|
|
191
|
-
await client.handleMessage?.(JSON.stringify(closeMessage));
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
expect(terminalService.getAllSessions()).toHaveLength(0);
|
|
195
|
-
}, 20000);
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
describe('错误处理和恢复', () => {
|
|
199
|
-
it('应该处理终端会话创建失败', async () => {
|
|
200
|
-
await webSocketService.start();
|
|
201
|
-
|
|
202
|
-
const mockConnection = {
|
|
203
|
-
clientId: 'error-test-client',
|
|
204
|
-
ws: {
|
|
205
|
-
readyState: 1,
|
|
206
|
-
send: vi.fn(),
|
|
207
|
-
close: vi.fn(),
|
|
208
|
-
on: vi.fn()
|
|
209
|
-
},
|
|
210
|
-
send: vi.fn(),
|
|
211
|
-
sendError: vi.fn()
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
webSocketService.connections.set(mockConnection.clientId, mockConnection);
|
|
215
|
-
|
|
216
|
-
// 尝试创建无效的会话(使用无效的工作目录)
|
|
217
|
-
const createMessage = {
|
|
218
|
-
type: 'terminal.create',
|
|
219
|
-
sessionId: 'invalid-session',
|
|
220
|
-
workingDirectory: '/invalid/directory/that/does/not/exist'
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
await mockConnection.handleMessage?.(JSON.stringify(createMessage));
|
|
224
|
-
|
|
225
|
-
// 验证错误消息已发送
|
|
226
|
-
expect(mockConnection.sendError).toHaveBeenCalled();
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
it('应该处理WebSocket连接中断', async () => {
|
|
230
|
-
await webSocketService.start();
|
|
231
|
-
|
|
232
|
-
// 创建一个会话
|
|
233
|
-
const session = await terminalService.createSession();
|
|
234
|
-
expect(session).toBeDefined();
|
|
235
|
-
|
|
236
|
-
// 模拟连接断开
|
|
237
|
-
const mockConnection = {
|
|
238
|
-
clientId: 'disconnect-test-client',
|
|
239
|
-
sessionId: session.id,
|
|
240
|
-
ws: {
|
|
241
|
-
readyState: 1,
|
|
242
|
-
send: vi.fn(),
|
|
243
|
-
close: vi.fn(),
|
|
244
|
-
on: vi.fn()
|
|
245
|
-
}
|
|
246
|
-
};
|
|
247
|
-
|
|
248
|
-
webSocketService.connections.set(mockConnection.clientId, mockConnection);
|
|
249
|
-
|
|
250
|
-
// 触发连接关闭处理
|
|
251
|
-
mockConnection.ws.readyState = 3; // WebSocket.CLOSED
|
|
252
|
-
mockConnection.handleClose?.(1000, 'Normal closure');
|
|
253
|
-
|
|
254
|
-
// 验证会话已被清理
|
|
255
|
-
setTimeout(() => {
|
|
256
|
-
expect(terminalService.hasSession(session.id)).toBe(false);
|
|
257
|
-
}, 100);
|
|
258
|
-
});
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
describe('性能和资源管理', () => {
|
|
262
|
-
it('应该限制最大并发会话数', async () => {
|
|
263
|
-
// 创建有限制配置的服务
|
|
264
|
-
const limitedService = new (await import('../../services/TerminalService.js')).TerminalService({
|
|
265
|
-
maxSessions: 2
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
// 创建最大数量的会话
|
|
269
|
-
await limitedService.createSession();
|
|
270
|
-
await limitedService.createSession();
|
|
271
|
-
|
|
272
|
-
// 尝试创建超出限制的会话
|
|
273
|
-
await expect(limitedService.createSession()).rejects.toThrow('Maximum sessions limit reached');
|
|
274
|
-
|
|
275
|
-
await limitedService.shutdown();
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
it('应该清理超时的会话', async () => {
|
|
279
|
-
const timeoutService = new (await import('../../services/TerminalService.js')).TerminalService({
|
|
280
|
-
timeout: 100 // 100ms超时
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
// 创建会话
|
|
284
|
-
const session = await timeoutService.createSession();
|
|
285
|
-
expect(session).toBeDefined();
|
|
286
|
-
|
|
287
|
-
// 手动设置会话为非活跃状态
|
|
288
|
-
session.lastActivity = new Date(Date.now() - 200); // 超过超时时间
|
|
289
|
-
|
|
290
|
-
// 手动触发清理
|
|
291
|
-
timeoutService.cleanupInactiveSessions();
|
|
292
|
-
|
|
293
|
-
// 验证会话已被清理
|
|
294
|
-
expect(timeoutService.getAllSessions()).toHaveLength(0);
|
|
295
|
-
|
|
296
|
-
await timeoutService.shutdown();
|
|
297
|
-
});
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
describe('跨平台兼容性', () => {
|
|
301
|
-
it('应该在不同平台上创建正确的Shell命令', async () => {
|
|
302
|
-
const platform = process.platform;
|
|
303
|
-
|
|
304
|
-
// 创建会话
|
|
305
|
-
const session = await terminalService.createSession();
|
|
306
|
-
|
|
307
|
-
// 验证Shell命令
|
|
308
|
-
const expectedShell = platform === 'win32'
|
|
309
|
-
? process.env.COMSPEC || 'cmd.exe'
|
|
310
|
-
: process.env.SHELL || '/bin/bash';
|
|
311
|
-
|
|
312
|
-
expect(session.shell).toContain(expectedShell.split('/').pop() || expectedShell.split('\\').pop());
|
|
313
|
-
|
|
314
|
-
await terminalService.removeSession(session.id);
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
it('应该执行跨平台命令', async () => {
|
|
318
|
-
// 测试基本命令
|
|
319
|
-
const result = await terminalService.executeCommand('echo "cross-platform test"');
|
|
320
|
-
|
|
321
|
-
expect(result).toBeDefined();
|
|
322
|
-
expect(typeof result.exitCode).toBe('number');
|
|
323
|
-
expect(result.stdout).toContain('cross-platform test');
|
|
324
|
-
});
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
describe('安全性', () => {
|
|
328
|
-
it('应该限制命令执行权限', async () => {
|
|
329
|
-
// 尝试执行危险命令(应该被限制或失败)
|
|
330
|
-
const dangerousCommands = [
|
|
331
|
-
'rm -rf /',
|
|
332
|
-
'format c:',
|
|
333
|
-
'sudo rm -rf /'
|
|
334
|
-
];
|
|
335
|
-
|
|
336
|
-
for (const command of dangerousCommands) {
|
|
337
|
-
// 这些命令应该失败或被阻止
|
|
338
|
-
try {
|
|
339
|
-
const result = await terminalService.executeCommand(command);
|
|
340
|
-
// 如果没有抛出错误,至少应该有非零退出码
|
|
341
|
-
expect(result.exitCode).not.toBe(0);
|
|
342
|
-
} catch (error) {
|
|
343
|
-
// 或者应该抛出权限错误
|
|
344
|
-
expect(error.message).toContain('permission') ||
|
|
345
|
-
expect(error.message).toContain('denied') ||
|
|
346
|
-
expect(error.message).toContain('forbidden');
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
it('应该限制文件系统访问', async () => {
|
|
352
|
-
// 尝试访问系统敏感目录
|
|
353
|
-
const sensitivePaths = [
|
|
354
|
-
'/etc/passwd',
|
|
355
|
-
'C:\\Windows\\System32\\config',
|
|
356
|
-
'/etc/shadow'
|
|
357
|
-
];
|
|
358
|
-
|
|
359
|
-
for (const path of sensitivePaths) {
|
|
360
|
-
try {
|
|
361
|
-
const result = await terminalService.executeCommand(`cat ${path}`);
|
|
362
|
-
// 如果成功访问,应该返回空或权限错误
|
|
363
|
-
expect(result.exitCode).not.toBe(0);
|
|
364
|
-
} catch (error) {
|
|
365
|
-
// 应该抛出权限错误
|
|
366
|
-
expect(error.message).toContain('permission') ||
|
|
367
|
-
expect(error.message).toContain('denied');
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
});
|
|
371
|
-
});
|
|
372
|
-
});
|