@yivan-lab/pretty-please 1.4.0 → 1.5.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 +32 -4
- package/bin/pls.tsx +153 -35
- package/dist/bin/pls.js +126 -23
- package/dist/package.json +10 -2
- package/dist/src/__integration__/command-generation.test.d.ts +5 -0
- package/dist/src/__integration__/command-generation.test.js +508 -0
- package/dist/src/__integration__/error-recovery.test.d.ts +5 -0
- package/dist/src/__integration__/error-recovery.test.js +511 -0
- package/dist/src/__integration__/shell-hook-workflow.test.d.ts +5 -0
- package/dist/src/__integration__/shell-hook-workflow.test.js +375 -0
- package/dist/src/__tests__/alias.test.d.ts +5 -0
- package/dist/src/__tests__/alias.test.js +421 -0
- package/dist/src/__tests__/chat-history.test.d.ts +5 -0
- package/dist/src/__tests__/chat-history.test.js +372 -0
- package/dist/src/__tests__/config.test.d.ts +5 -0
- package/dist/src/__tests__/config.test.js +822 -0
- package/dist/src/__tests__/history.test.d.ts +5 -0
- package/dist/src/__tests__/history.test.js +439 -0
- package/dist/src/__tests__/remote-history.test.d.ts +5 -0
- package/dist/src/__tests__/remote-history.test.js +641 -0
- package/dist/src/__tests__/remote.test.d.ts +5 -0
- package/dist/src/__tests__/remote.test.js +689 -0
- package/dist/src/__tests__/shell-hook-install.test.d.ts +5 -0
- package/dist/src/__tests__/shell-hook-install.test.js +413 -0
- package/dist/src/__tests__/shell-hook-remote.test.d.ts +5 -0
- package/dist/src/__tests__/shell-hook-remote.test.js +507 -0
- package/dist/src/__tests__/shell-hook.test.d.ts +5 -0
- package/dist/src/__tests__/shell-hook.test.js +440 -0
- package/dist/src/__tests__/sysinfo.test.d.ts +5 -0
- package/dist/src/__tests__/sysinfo.test.js +572 -0
- package/dist/src/__tests__/system-history.test.d.ts +5 -0
- package/dist/src/__tests__/system-history.test.js +457 -0
- package/dist/src/components/Chat.js +9 -28
- package/dist/src/config.d.ts +2 -0
- package/dist/src/config.js +30 -2
- package/dist/src/mastra-chat.js +6 -3
- package/dist/src/multi-step.js +6 -3
- package/dist/src/project-context.d.ts +22 -0
- package/dist/src/project-context.js +168 -0
- package/dist/src/prompts.d.ts +4 -4
- package/dist/src/prompts.js +23 -6
- package/dist/src/shell-hook.d.ts +13 -0
- package/dist/src/shell-hook.js +163 -33
- package/dist/src/sysinfo.d.ts +38 -9
- package/dist/src/sysinfo.js +245 -21
- package/dist/src/system-history.d.ts +5 -0
- package/dist/src/system-history.js +64 -18
- package/dist/src/ui/__tests__/theme.test.d.ts +5 -0
- package/dist/src/ui/__tests__/theme.test.js +688 -0
- package/dist/src/upgrade.js +3 -0
- package/dist/src/user-preferences.d.ts +44 -0
- package/dist/src/user-preferences.js +147 -0
- package/dist/src/utils/__tests__/platform-capabilities.test.d.ts +5 -0
- package/dist/src/utils/__tests__/platform-capabilities.test.js +214 -0
- package/dist/src/utils/__tests__/platform-exec.test.d.ts +5 -0
- package/dist/src/utils/__tests__/platform-exec.test.js +212 -0
- package/dist/src/utils/__tests__/platform-shell.test.d.ts +5 -0
- package/dist/src/utils/__tests__/platform-shell.test.js +300 -0
- package/dist/src/utils/__tests__/platform.test.d.ts +5 -0
- package/dist/src/utils/__tests__/platform.test.js +137 -0
- package/dist/src/utils/platform.d.ts +88 -0
- package/dist/src/utils/platform.js +331 -0
- package/package.json +10 -2
- package/src/__integration__/command-generation.test.ts +602 -0
- package/src/__integration__/error-recovery.test.ts +620 -0
- package/src/__integration__/shell-hook-workflow.test.ts +457 -0
- package/src/__tests__/alias.test.ts +545 -0
- package/src/__tests__/chat-history.test.ts +462 -0
- package/src/__tests__/config.test.ts +1043 -0
- package/src/__tests__/history.test.ts +538 -0
- package/src/__tests__/remote-history.test.ts +791 -0
- package/src/__tests__/remote.test.ts +866 -0
- package/src/__tests__/shell-hook-install.test.ts +510 -0
- package/src/__tests__/shell-hook-remote.test.ts +679 -0
- package/src/__tests__/shell-hook.test.ts +564 -0
- package/src/__tests__/sysinfo.test.ts +718 -0
- package/src/__tests__/system-history.test.ts +608 -0
- package/src/components/Chat.tsx +10 -37
- package/src/config.ts +29 -2
- package/src/mastra-chat.ts +8 -3
- package/src/multi-step.ts +7 -2
- package/src/project-context.ts +191 -0
- package/src/prompts.ts +26 -5
- package/src/shell-hook.ts +179 -33
- package/src/sysinfo.ts +326 -25
- package/src/system-history.ts +67 -14
- package/src/ui/__tests__/theme.test.ts +869 -0
- package/src/upgrade.ts +5 -0
- package/src/user-preferences.ts +178 -0
- package/src/utils/__tests__/platform-capabilities.test.ts +265 -0
- package/src/utils/__tests__/platform-exec.test.ts +278 -0
- package/src/utils/__tests__/platform-shell.test.ts +353 -0
- package/src/utils/__tests__/platform.test.ts +170 -0
- package/src/utils/platform.ts +431 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shell Hook 工作流集成测试
|
|
3
|
+
* 测试完整的 Hook 安装、命令记录、配置变更、卸载恢复等工作流
|
|
4
|
+
*/
|
|
5
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
6
|
+
// Mock child_process 模块
|
|
7
|
+
vi.mock('child_process', () => ({
|
|
8
|
+
exec: vi.fn(),
|
|
9
|
+
execSync: vi.fn(),
|
|
10
|
+
spawn: vi.fn(),
|
|
11
|
+
}));
|
|
12
|
+
// Mock system-history 模块
|
|
13
|
+
vi.mock('../system-history.js', () => ({
|
|
14
|
+
getSystemShellHistory: vi.fn(() => []),
|
|
15
|
+
}));
|
|
16
|
+
// Mock fs 模块
|
|
17
|
+
vi.mock('fs', () => ({
|
|
18
|
+
default: {
|
|
19
|
+
existsSync: vi.fn(),
|
|
20
|
+
readFileSync: vi.fn(),
|
|
21
|
+
writeFileSync: vi.fn(),
|
|
22
|
+
appendFileSync: vi.fn(),
|
|
23
|
+
copyFileSync: vi.fn(),
|
|
24
|
+
mkdirSync: vi.fn(),
|
|
25
|
+
unlinkSync: vi.fn(),
|
|
26
|
+
rmSync: vi.fn(),
|
|
27
|
+
statSync: vi.fn(),
|
|
28
|
+
},
|
|
29
|
+
}));
|
|
30
|
+
// Mock os 模块
|
|
31
|
+
vi.mock('os', () => ({
|
|
32
|
+
default: {
|
|
33
|
+
homedir: vi.fn(() => '/home/testuser'),
|
|
34
|
+
platform: vi.fn(() => 'linux'),
|
|
35
|
+
type: vi.fn(() => 'Linux'),
|
|
36
|
+
release: vi.fn(() => '5.4.0'),
|
|
37
|
+
arch: vi.fn(() => 'x64'),
|
|
38
|
+
userInfo: vi.fn(() => ({ username: 'testuser' })),
|
|
39
|
+
hostname: vi.fn(() => 'testhost'),
|
|
40
|
+
},
|
|
41
|
+
}));
|
|
42
|
+
// Mock config 模块
|
|
43
|
+
const mockConfig = {
|
|
44
|
+
shellHook: false,
|
|
45
|
+
shellHistoryLimit: 20,
|
|
46
|
+
commandHistoryLimit: 50,
|
|
47
|
+
};
|
|
48
|
+
vi.mock('../config.js', () => ({
|
|
49
|
+
getConfig: vi.fn(() => mockConfig),
|
|
50
|
+
saveConfig: vi.fn(),
|
|
51
|
+
CONFIG_DIR: '/home/testuser/.please',
|
|
52
|
+
}));
|
|
53
|
+
// Mock theme 模块
|
|
54
|
+
vi.mock('../ui/theme.js', () => ({
|
|
55
|
+
getCurrentTheme: vi.fn(() => ({
|
|
56
|
+
primary: '#007acc',
|
|
57
|
+
secondary: '#6c757d',
|
|
58
|
+
success: '#4caf50',
|
|
59
|
+
error: '#f44336',
|
|
60
|
+
warning: '#ff9800',
|
|
61
|
+
text: { muted: '#666666' },
|
|
62
|
+
})),
|
|
63
|
+
}));
|
|
64
|
+
// Mock chalk
|
|
65
|
+
vi.mock('chalk', () => ({
|
|
66
|
+
default: {
|
|
67
|
+
bold: vi.fn((s) => s),
|
|
68
|
+
gray: vi.fn((s) => s),
|
|
69
|
+
dim: vi.fn((s) => s),
|
|
70
|
+
hex: vi.fn(() => (s) => s),
|
|
71
|
+
green: vi.fn((s) => s),
|
|
72
|
+
red: vi.fn((s) => s),
|
|
73
|
+
yellow: vi.fn((s) => s),
|
|
74
|
+
},
|
|
75
|
+
}));
|
|
76
|
+
import fs from 'fs';
|
|
77
|
+
import os from 'os';
|
|
78
|
+
import { exec } from 'child_process';
|
|
79
|
+
import { getConfig, saveConfig } from '../config.js';
|
|
80
|
+
const mockFs = vi.mocked(fs);
|
|
81
|
+
const mockOs = vi.mocked(os);
|
|
82
|
+
const mockExec = vi.mocked(exec);
|
|
83
|
+
const mockGetConfig = vi.mocked(getConfig);
|
|
84
|
+
const mockSaveConfig = vi.mocked(saveConfig);
|
|
85
|
+
// 重置模块辅助函数
|
|
86
|
+
async function resetModules() {
|
|
87
|
+
vi.resetModules();
|
|
88
|
+
return {
|
|
89
|
+
shellHook: await import('../shell-hook.js'),
|
|
90
|
+
history: await import('../history.js'),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
// 模拟 Shell 配置文件内容
|
|
94
|
+
const ZSHRC_TEMPLATE = `# User configuration
|
|
95
|
+
export PATH="/usr/local/bin:$PATH"
|
|
96
|
+
alias ll="ls -la"
|
|
97
|
+
`;
|
|
98
|
+
const ZSHRC_WITH_HOOK = `# User configuration
|
|
99
|
+
export PATH="/usr/local/bin:$PATH"
|
|
100
|
+
alias ll="ls -la"
|
|
101
|
+
|
|
102
|
+
# >>> pretty-please shell hook >>>
|
|
103
|
+
# 此代码块由 pls 自动生成,请勿手动修改
|
|
104
|
+
autoload -Uz add-zsh-hook
|
|
105
|
+
_pls_preexec() {
|
|
106
|
+
export _PLS_LAST_CMD="$1"
|
|
107
|
+
export _PLS_CMD_START=$(date +%s)
|
|
108
|
+
}
|
|
109
|
+
_pls_precmd() {
|
|
110
|
+
local exit_code=$?
|
|
111
|
+
# Hook code...
|
|
112
|
+
}
|
|
113
|
+
add-zsh-hook preexec _pls_preexec
|
|
114
|
+
add-zsh-hook precmd _pls_precmd
|
|
115
|
+
# <<< pretty-please shell hook <<<
|
|
116
|
+
`;
|
|
117
|
+
// 模拟 Shell 历史记录(JSONL 格式)
|
|
118
|
+
const SHELL_HISTORY_JSONL = `{"cmd":"ls -la","exit":0,"time":"2024-01-01T10:00:00.000Z"}
|
|
119
|
+
{"cmd":"cd /home","exit":0,"time":"2024-01-01T10:01:00.000Z"}
|
|
120
|
+
{"cmd":"git status","exit":0,"time":"2024-01-01T10:02:00.000Z"}
|
|
121
|
+
{"cmd":"invalid-command","exit":127,"time":"2024-01-01T10:03:00.000Z"}
|
|
122
|
+
{"cmd":"pls check disk","exit":0,"time":"2024-01-01T10:04:00.000Z"}
|
|
123
|
+
`;
|
|
124
|
+
beforeEach(() => {
|
|
125
|
+
vi.clearAllMocks();
|
|
126
|
+
mockOs.homedir.mockReturnValue('/home/testuser');
|
|
127
|
+
mockOs.platform.mockReturnValue('linux');
|
|
128
|
+
mockFs.mkdirSync.mockImplementation(() => undefined);
|
|
129
|
+
mockFs.writeFileSync.mockImplementation(() => { });
|
|
130
|
+
mockFs.appendFileSync.mockImplementation(() => { });
|
|
131
|
+
mockFs.copyFileSync.mockImplementation(() => { });
|
|
132
|
+
mockSaveConfig.mockImplementation(() => { });
|
|
133
|
+
// 重置配置状态
|
|
134
|
+
Object.assign(mockConfig, {
|
|
135
|
+
shellHook: false,
|
|
136
|
+
shellHistoryLimit: 20,
|
|
137
|
+
commandHistoryLimit: 50,
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
afterEach(() => {
|
|
141
|
+
vi.restoreAllMocks();
|
|
142
|
+
});
|
|
143
|
+
// ============================================================================
|
|
144
|
+
// 完整安装流程测试
|
|
145
|
+
// ============================================================================
|
|
146
|
+
describe('Shell Hook 完整安装流程', () => {
|
|
147
|
+
it('首次安装应该: 检测Shell → getHookStatus 返回未安装', async () => {
|
|
148
|
+
// 设置环境: Zsh shell, 配置文件存在但无 Hook
|
|
149
|
+
process.env.SHELL = '/bin/zsh';
|
|
150
|
+
mockConfig.shellHook = false;
|
|
151
|
+
mockFs.existsSync.mockImplementation((path) => {
|
|
152
|
+
if (path.includes('.zshrc'))
|
|
153
|
+
return true;
|
|
154
|
+
if (path.includes('.please'))
|
|
155
|
+
return true;
|
|
156
|
+
if (path.includes('shell_history'))
|
|
157
|
+
return false;
|
|
158
|
+
return false;
|
|
159
|
+
});
|
|
160
|
+
mockFs.readFileSync.mockImplementation((path) => {
|
|
161
|
+
if (path.toString().includes('.zshrc'))
|
|
162
|
+
return ZSHRC_TEMPLATE;
|
|
163
|
+
return '';
|
|
164
|
+
});
|
|
165
|
+
const { shellHook } = await resetModules();
|
|
166
|
+
// 验证 Hook 状态检测
|
|
167
|
+
const status = shellHook.getHookStatus();
|
|
168
|
+
expect(status.installed).toBe(false);
|
|
169
|
+
});
|
|
170
|
+
it('已安装时 getHookStatus 应该返回 installed: true', async () => {
|
|
171
|
+
// 确保使用 Unix 平台检测逻辑
|
|
172
|
+
Object.defineProperty(process, 'platform', { value: 'linux' });
|
|
173
|
+
process.env.SHELL = '/bin/zsh';
|
|
174
|
+
mockConfig.shellHook = true;
|
|
175
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
176
|
+
mockFs.readFileSync.mockImplementation((path) => {
|
|
177
|
+
if (path.toString().includes('.zshrc'))
|
|
178
|
+
return ZSHRC_WITH_HOOK;
|
|
179
|
+
return '';
|
|
180
|
+
});
|
|
181
|
+
const { shellHook } = await resetModules();
|
|
182
|
+
// 检测是否已安装
|
|
183
|
+
const status = shellHook.getHookStatus();
|
|
184
|
+
expect(status.installed).toBe(true);
|
|
185
|
+
});
|
|
186
|
+
it('Zsh 应该使用 .zshrc 配置文件', async () => {
|
|
187
|
+
const { shellHook } = await resetModules();
|
|
188
|
+
// Zsh (跨平台)
|
|
189
|
+
expect(shellHook.getShellConfigPath('zsh')).toContain('.zshrc');
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
// ============================================================================
|
|
193
|
+
// 命令记录流程测试
|
|
194
|
+
// ============================================================================
|
|
195
|
+
describe('命令记录流程', () => {
|
|
196
|
+
it('应该正确解析 JSONL 格式的 Shell 历史', async () => {
|
|
197
|
+
mockConfig.shellHook = true;
|
|
198
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
199
|
+
mockFs.readFileSync.mockReturnValue(SHELL_HISTORY_JSONL);
|
|
200
|
+
const { shellHook } = await resetModules();
|
|
201
|
+
const history = shellHook.getShellHistory();
|
|
202
|
+
expect(history.length).toBeGreaterThan(0);
|
|
203
|
+
expect(history[0]).toHaveProperty('cmd');
|
|
204
|
+
expect(history[0]).toHaveProperty('exit');
|
|
205
|
+
expect(history[0]).toHaveProperty('time');
|
|
206
|
+
});
|
|
207
|
+
it('shellHook 未启用时应该返回空数组', async () => {
|
|
208
|
+
mockConfig.shellHook = false;
|
|
209
|
+
const { shellHook } = await resetModules();
|
|
210
|
+
const history = shellHook.getShellHistory();
|
|
211
|
+
expect(history).toEqual([]);
|
|
212
|
+
});
|
|
213
|
+
it('历史文件不存在时应该返回空数组', async () => {
|
|
214
|
+
mockConfig.shellHook = true;
|
|
215
|
+
mockFs.existsSync.mockReturnValue(false);
|
|
216
|
+
const { shellHook } = await resetModules();
|
|
217
|
+
const history = shellHook.getShellHistory();
|
|
218
|
+
expect(history).toEqual([]);
|
|
219
|
+
});
|
|
220
|
+
it('应该按 shellHistoryLimit 限制返回数量', async () => {
|
|
221
|
+
mockConfig.shellHook = true;
|
|
222
|
+
mockConfig.shellHistoryLimit = 3;
|
|
223
|
+
// 创建 10 条历史记录
|
|
224
|
+
const manyRecords = Array.from({ length: 10 }, (_, i) => JSON.stringify({ cmd: `cmd${i}`, exit: 0, time: new Date().toISOString() })).join('\n');
|
|
225
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
226
|
+
mockFs.readFileSync.mockReturnValue(manyRecords);
|
|
227
|
+
const { shellHook } = await resetModules();
|
|
228
|
+
const history = shellHook.getShellHistory();
|
|
229
|
+
expect(history.length).toBeLessThanOrEqual(3);
|
|
230
|
+
});
|
|
231
|
+
it('应该跳过无效的 JSON 行', async () => {
|
|
232
|
+
mockConfig.shellHook = true;
|
|
233
|
+
const invalidJsonl = `{"cmd":"valid1","exit":0,"time":"2024-01-01"}
|
|
234
|
+
invalid line here
|
|
235
|
+
{"cmd":"valid2","exit":0,"time":"2024-01-01"}
|
|
236
|
+
{broken json
|
|
237
|
+
{"cmd":"valid3","exit":0,"time":"2024-01-01"}`;
|
|
238
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
239
|
+
mockFs.readFileSync.mockReturnValue(invalidJsonl);
|
|
240
|
+
const { shellHook } = await resetModules();
|
|
241
|
+
const history = shellHook.getShellHistory();
|
|
242
|
+
expect(history.length).toBe(3); // 只有 3 条有效记录
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
// ============================================================================
|
|
246
|
+
// AI 格式化流程测试
|
|
247
|
+
// ============================================================================
|
|
248
|
+
describe('Shell 历史 AI 格式化', () => {
|
|
249
|
+
it('formatShellHistoryForAI 应该包含命令和状态', async () => {
|
|
250
|
+
mockConfig.shellHook = true;
|
|
251
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
252
|
+
mockFs.readFileSync.mockReturnValue(SHELL_HISTORY_JSONL);
|
|
253
|
+
const { shellHook } = await resetModules();
|
|
254
|
+
const formatted = shellHook.formatShellHistoryForAI();
|
|
255
|
+
expect(formatted).toContain('ls -la');
|
|
256
|
+
expect(formatted).toContain('git status');
|
|
257
|
+
});
|
|
258
|
+
it('失败命令应该显示退出码', async () => {
|
|
259
|
+
mockConfig.shellHook = true;
|
|
260
|
+
const failedCommand = `{"cmd":"invalid-command","exit":127,"time":"2024-01-01"}`;
|
|
261
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
262
|
+
mockFs.readFileSync.mockReturnValue(failedCommand);
|
|
263
|
+
const { shellHook } = await resetModules();
|
|
264
|
+
const formatted = shellHook.formatShellHistoryForAI();
|
|
265
|
+
expect(formatted).toContain('127');
|
|
266
|
+
});
|
|
267
|
+
it('空历史应该返回空字符串', async () => {
|
|
268
|
+
mockConfig.shellHook = true;
|
|
269
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
270
|
+
mockFs.readFileSync.mockReturnValue('');
|
|
271
|
+
const { shellHook } = await resetModules();
|
|
272
|
+
const formatted = shellHook.formatShellHistoryForAI();
|
|
273
|
+
expect(formatted).toBe('');
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
// ============================================================================
|
|
277
|
+
// pls 命令历史与 Shell 历史关联测试
|
|
278
|
+
// ============================================================================
|
|
279
|
+
describe('pls 命令历史与 Shell 历史关联', () => {
|
|
280
|
+
it('addHistory 应该记录用户修改标记', async () => {
|
|
281
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
282
|
+
mockFs.readFileSync.mockReturnValue(JSON.stringify([]));
|
|
283
|
+
let savedHistory = [];
|
|
284
|
+
mockFs.writeFileSync.mockImplementation((path, content) => {
|
|
285
|
+
if (path.toString().includes('history.json')) {
|
|
286
|
+
savedHistory = JSON.parse(content);
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
const { history } = await resetModules();
|
|
290
|
+
history.addHistory({
|
|
291
|
+
userPrompt: '检查磁盘',
|
|
292
|
+
command: 'df -h /home',
|
|
293
|
+
aiGeneratedCommand: 'df -h',
|
|
294
|
+
userModified: true,
|
|
295
|
+
executed: true,
|
|
296
|
+
exitCode: 0,
|
|
297
|
+
output: '',
|
|
298
|
+
});
|
|
299
|
+
expect(savedHistory.length).toBe(1);
|
|
300
|
+
expect(savedHistory[0].userModified).toBe(true);
|
|
301
|
+
expect(savedHistory[0].aiGeneratedCommand).toBe('df -h');
|
|
302
|
+
});
|
|
303
|
+
it('addHistory 应该记录 builtin 原因', async () => {
|
|
304
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
305
|
+
mockFs.readFileSync.mockReturnValue(JSON.stringify([]));
|
|
306
|
+
let savedHistory = [];
|
|
307
|
+
mockFs.writeFileSync.mockImplementation((path, content) => {
|
|
308
|
+
if (path.toString().includes('history.json')) {
|
|
309
|
+
savedHistory = JSON.parse(content);
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
const { history } = await resetModules();
|
|
313
|
+
history.addHistory({
|
|
314
|
+
userPrompt: '删除文件',
|
|
315
|
+
command: 'rm -rf important',
|
|
316
|
+
executed: false,
|
|
317
|
+
exitCode: null,
|
|
318
|
+
output: '',
|
|
319
|
+
reason: 'builtin',
|
|
320
|
+
});
|
|
321
|
+
expect(savedHistory[0].executed).toBe(false);
|
|
322
|
+
expect(savedHistory[0].reason).toBe('builtin');
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
// ============================================================================
|
|
326
|
+
// 配置变更流程测试
|
|
327
|
+
// ============================================================================
|
|
328
|
+
describe('配置变更影响 Hook 行为', () => {
|
|
329
|
+
it('修改 shellHistoryLimit 应该影响历史返回数量', async () => {
|
|
330
|
+
mockConfig.shellHook = true;
|
|
331
|
+
// 创建 50 条记录
|
|
332
|
+
const records = Array.from({ length: 50 }, (_, i) => JSON.stringify({ cmd: `cmd${i}`, exit: 0, time: new Date().toISOString() })).join('\n');
|
|
333
|
+
mockFs.existsSync.mockReturnValue(true);
|
|
334
|
+
mockFs.readFileSync.mockReturnValue(records);
|
|
335
|
+
// 限制为 10
|
|
336
|
+
mockConfig.shellHistoryLimit = 10;
|
|
337
|
+
const { shellHook } = await resetModules();
|
|
338
|
+
const history10 = shellHook.getShellHistory();
|
|
339
|
+
expect(history10.length).toBeLessThanOrEqual(10);
|
|
340
|
+
// 限制为 30
|
|
341
|
+
mockConfig.shellHistoryLimit = 30;
|
|
342
|
+
const { shellHook: shellHook2 } = await resetModules();
|
|
343
|
+
const history30 = shellHook2.getShellHistory();
|
|
344
|
+
expect(history30.length).toBeLessThanOrEqual(30);
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
// ============================================================================
|
|
348
|
+
// Shell 检测测试
|
|
349
|
+
// ============================================================================
|
|
350
|
+
describe('Shell 类型检测', () => {
|
|
351
|
+
const originalPlatform = process.platform;
|
|
352
|
+
beforeEach(() => {
|
|
353
|
+
// Stub process.platform 为 Linux(非 Windows)
|
|
354
|
+
Object.defineProperty(process, 'platform', { value: 'linux' });
|
|
355
|
+
});
|
|
356
|
+
afterEach(() => {
|
|
357
|
+
Object.defineProperty(process, 'platform', { value: originalPlatform });
|
|
358
|
+
});
|
|
359
|
+
it('应该从 SHELL 环境变量检测 Zsh', async () => {
|
|
360
|
+
process.env.SHELL = '/bin/zsh';
|
|
361
|
+
const { shellHook } = await resetModules();
|
|
362
|
+
expect(shellHook.detectShell()).toBe('zsh');
|
|
363
|
+
});
|
|
364
|
+
it('应该从 SHELL 环境变量检测 Bash', async () => {
|
|
365
|
+
process.env.SHELL = '/bin/bash';
|
|
366
|
+
const { shellHook } = await resetModules();
|
|
367
|
+
expect(shellHook.detectShell()).toBe('bash');
|
|
368
|
+
});
|
|
369
|
+
it('getShellConfigPath 对于不支持的 Shell 应该返回 null', async () => {
|
|
370
|
+
const { shellHook } = await resetModules();
|
|
371
|
+
// 'cmd' 不是支持的 shell
|
|
372
|
+
const path = shellHook.getShellConfigPath('cmd');
|
|
373
|
+
expect(path).toBeNull();
|
|
374
|
+
});
|
|
375
|
+
});
|