@xiaozhi-client/cli 1.9.4-beta.5
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/LICENSE +21 -0
- package/README.md +98 -0
- package/fix-imports.js +32 -0
- package/package.json +26 -0
- package/project.json +75 -0
- package/src/Constants.ts +105 -0
- package/src/Container.ts +212 -0
- package/src/Types.ts +79 -0
- package/src/commands/CommandHandlerFactory.ts +98 -0
- package/src/commands/ConfigCommandHandler.ts +279 -0
- package/src/commands/EndpointCommandHandler.ts +158 -0
- package/src/commands/McpCommandHandler.ts +778 -0
- package/src/commands/ProjectCommandHandler.ts +254 -0
- package/src/commands/ServiceCommandHandler.ts +182 -0
- package/src/commands/__tests__/CommandHandlerFactory.test.ts +323 -0
- package/src/commands/__tests__/CommandRegistry.test.ts +287 -0
- package/src/commands/__tests__/ConfigCommandHandler.test.ts +844 -0
- package/src/commands/__tests__/EndpointCommandHandler.test.ts +426 -0
- package/src/commands/__tests__/McpCommandHandler.test.ts +753 -0
- package/src/commands/__tests__/ProjectCommandHandler.test.ts +230 -0
- package/src/commands/__tests__/ServiceCommands.integration.test.ts +408 -0
- package/src/commands/index.ts +351 -0
- package/src/errors/ErrorHandlers.ts +141 -0
- package/src/errors/ErrorMessages.ts +121 -0
- package/src/errors/__tests__/index.test.ts +186 -0
- package/src/errors/index.ts +163 -0
- package/src/global.d.ts +19 -0
- package/src/index.ts +53 -0
- package/src/interfaces/Command.ts +128 -0
- package/src/interfaces/CommandTypes.ts +95 -0
- package/src/interfaces/Config.ts +25 -0
- package/src/interfaces/Service.ts +99 -0
- package/src/services/DaemonManager.ts +318 -0
- package/src/services/ProcessManager.ts +235 -0
- package/src/services/ServiceManager.ts +319 -0
- package/src/services/TemplateManager.ts +382 -0
- package/src/services/__tests__/DaemonManager.test.ts +378 -0
- package/src/services/__tests__/DaemonMode.integration.test.ts +321 -0
- package/src/services/__tests__/ProcessManager.test.ts +296 -0
- package/src/services/__tests__/ServiceManager.test.ts +774 -0
- package/src/services/__tests__/TemplateManager.test.ts +337 -0
- package/src/types/backend.d.ts +48 -0
- package/src/utils/FileUtils.ts +320 -0
- package/src/utils/FormatUtils.ts +198 -0
- package/src/utils/PathUtils.ts +255 -0
- package/src/utils/PlatformUtils.ts +217 -0
- package/src/utils/Validation.ts +274 -0
- package/src/utils/VersionUtils.ts +141 -0
- package/src/utils/__tests__/FileUtils.test.ts +728 -0
- package/src/utils/__tests__/FormatUtils.test.ts +243 -0
- package/src/utils/__tests__/PathUtils.test.ts +1165 -0
- package/src/utils/__tests__/PlatformUtils.test.ts +723 -0
- package/src/utils/__tests__/Validation.test.ts +560 -0
- package/src/utils/__tests__/VersionUtils.test.ts +410 -0
- package/tsconfig.json +32 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/tsup.config.ts +107 -0
- package/vitest.config.ts +97 -0
|
@@ -0,0 +1,723 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 平台相关工具单元测试
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { execSync } from "node:child_process";
|
|
6
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
7
|
+
import { ProcessError } from "../../errors/index";
|
|
8
|
+
import { PlatformUtils } from "../PlatformUtils";
|
|
9
|
+
|
|
10
|
+
// Mock child_process module
|
|
11
|
+
vi.mock("node:child_process");
|
|
12
|
+
const mockedExecSync = vi.mocked(execSync);
|
|
13
|
+
|
|
14
|
+
describe("PlatformUtils", () => {
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
vi.clearAllMocks();
|
|
17
|
+
// Reset process.env mock
|
|
18
|
+
vi.unstubAllEnvs();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
vi.restoreAllMocks();
|
|
23
|
+
vi.unstubAllEnvs();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe("获取当前平台", () => {
|
|
27
|
+
it("应返回当前平台", () => {
|
|
28
|
+
// Since we can't easily change process.platform, we just test it returns a string
|
|
29
|
+
const result = PlatformUtils.getCurrentPlatform();
|
|
30
|
+
expect(typeof result).toBe("string");
|
|
31
|
+
expect([
|
|
32
|
+
"win32",
|
|
33
|
+
"darwin",
|
|
34
|
+
"linux",
|
|
35
|
+
"freebsd",
|
|
36
|
+
"openbsd",
|
|
37
|
+
"sunos",
|
|
38
|
+
"aix",
|
|
39
|
+
]).toContain(result);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe("是否为Windows系统", () => {
|
|
44
|
+
it("在Windows上应返回 true", () => {
|
|
45
|
+
// Mock process.platform
|
|
46
|
+
const originalPlatform = process.platform;
|
|
47
|
+
Object.defineProperty(process, "platform", {
|
|
48
|
+
value: "win32",
|
|
49
|
+
writable: true,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
expect(PlatformUtils.isWindows()).toBe(true);
|
|
53
|
+
|
|
54
|
+
// Restore original platform
|
|
55
|
+
Object.defineProperty(process, "platform", {
|
|
56
|
+
value: originalPlatform,
|
|
57
|
+
writable: true,
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("在非Windows平台上应返回 false", () => {
|
|
62
|
+
const originalPlatform = process.platform;
|
|
63
|
+
Object.defineProperty(process, "platform", {
|
|
64
|
+
value: "darwin",
|
|
65
|
+
writable: true,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
expect(PlatformUtils.isWindows()).toBe(false);
|
|
69
|
+
|
|
70
|
+
Object.defineProperty(process, "platform", {
|
|
71
|
+
value: originalPlatform,
|
|
72
|
+
writable: true,
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe("是否为macOS系统", () => {
|
|
78
|
+
it("在macOS上应返回 true", () => {
|
|
79
|
+
const originalPlatform = process.platform;
|
|
80
|
+
Object.defineProperty(process, "platform", {
|
|
81
|
+
value: "darwin",
|
|
82
|
+
writable: true,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
expect(PlatformUtils.isMacOS()).toBe(true);
|
|
86
|
+
|
|
87
|
+
Object.defineProperty(process, "platform", {
|
|
88
|
+
value: originalPlatform,
|
|
89
|
+
writable: true,
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("在非macOS平台上应返回 false", () => {
|
|
94
|
+
const originalPlatform = process.platform;
|
|
95
|
+
Object.defineProperty(process, "platform", {
|
|
96
|
+
value: "linux",
|
|
97
|
+
writable: true,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
expect(PlatformUtils.isMacOS()).toBe(false);
|
|
101
|
+
|
|
102
|
+
Object.defineProperty(process, "platform", {
|
|
103
|
+
value: originalPlatform,
|
|
104
|
+
writable: true,
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe("是否为Linux系统", () => {
|
|
110
|
+
it("在Linux上应返回 true", () => {
|
|
111
|
+
const originalPlatform = process.platform;
|
|
112
|
+
Object.defineProperty(process, "platform", {
|
|
113
|
+
value: "linux",
|
|
114
|
+
writable: true,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
expect(PlatformUtils.isLinux()).toBe(true);
|
|
118
|
+
|
|
119
|
+
Object.defineProperty(process, "platform", {
|
|
120
|
+
value: originalPlatform,
|
|
121
|
+
writable: true,
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("在非Linux平台上应返回 false", () => {
|
|
126
|
+
const originalPlatform = process.platform;
|
|
127
|
+
Object.defineProperty(process, "platform", {
|
|
128
|
+
value: "win32",
|
|
129
|
+
writable: true,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
expect(PlatformUtils.isLinux()).toBe(false);
|
|
133
|
+
|
|
134
|
+
Object.defineProperty(process, "platform", {
|
|
135
|
+
value: originalPlatform,
|
|
136
|
+
writable: true,
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe("是否为类Unix系统", () => {
|
|
142
|
+
it("在类Unix系统上应返回 true", () => {
|
|
143
|
+
const originalPlatform = process.platform;
|
|
144
|
+
Object.defineProperty(process, "platform", {
|
|
145
|
+
value: "darwin",
|
|
146
|
+
writable: true,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
expect(PlatformUtils.isUnixLike()).toBe(true);
|
|
150
|
+
|
|
151
|
+
Object.defineProperty(process, "platform", {
|
|
152
|
+
value: originalPlatform,
|
|
153
|
+
writable: true,
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("在Windows上应返回 false", () => {
|
|
158
|
+
const originalPlatform = process.platform;
|
|
159
|
+
Object.defineProperty(process, "platform", {
|
|
160
|
+
value: "win32",
|
|
161
|
+
writable: true,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
expect(PlatformUtils.isUnixLike()).toBe(false);
|
|
165
|
+
|
|
166
|
+
Object.defineProperty(process, "platform", {
|
|
167
|
+
value: originalPlatform,
|
|
168
|
+
writable: true,
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
describe("是否为小智进程", () => {
|
|
174
|
+
const testPid = 1234;
|
|
175
|
+
|
|
176
|
+
beforeEach(() => {
|
|
177
|
+
// Mock process.kill
|
|
178
|
+
vi.spyOn(process, "kill").mockImplementation(() => true);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
afterEach(() => {
|
|
182
|
+
vi.restoreAllMocks();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("在容器环境中进程存在时应返回 true", () => {
|
|
186
|
+
vi.stubEnv("XIAOZHI_CONTAINER", "true");
|
|
187
|
+
|
|
188
|
+
const result = PlatformUtils.isXiaozhiProcess(testPid);
|
|
189
|
+
|
|
190
|
+
expect(result).toBe(true);
|
|
191
|
+
expect(process.kill).toHaveBeenCalledWith(testPid, 0);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("在测试环境中进程存在时应返回 true", () => {
|
|
195
|
+
vi.stubEnv("NODE_ENV", "test");
|
|
196
|
+
vi.stubEnv("XIAOZHI_CONTAINER", "false");
|
|
197
|
+
|
|
198
|
+
const result = PlatformUtils.isXiaozhiProcess(testPid);
|
|
199
|
+
|
|
200
|
+
expect(result).toBe(true);
|
|
201
|
+
expect(process.kill).toHaveBeenCalledWith(testPid, 0);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("在容器环境中进程不存在时应返回 false", () => {
|
|
205
|
+
vi.stubEnv("XIAOZHI_CONTAINER", "true");
|
|
206
|
+
vi.spyOn(process, "kill").mockImplementation(() => {
|
|
207
|
+
throw new Error("Process not found");
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
const result = PlatformUtils.isXiaozhiProcess(testPid);
|
|
211
|
+
|
|
212
|
+
expect(result).toBe(false);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it("应在Windows上检查进程命令行", () => {
|
|
216
|
+
const originalPlatform = process.platform;
|
|
217
|
+
Object.defineProperty(process, "platform", {
|
|
218
|
+
value: "win32",
|
|
219
|
+
writable: true,
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
vi.stubEnv("XIAOZHI_CONTAINER", "false");
|
|
223
|
+
vi.stubEnv("NODE_ENV", "production");
|
|
224
|
+
mockedExecSync.mockReturnValue('"node.exe","xiaozhi-client.js"\r\n');
|
|
225
|
+
|
|
226
|
+
const result = PlatformUtils.isXiaozhiProcess(testPid);
|
|
227
|
+
|
|
228
|
+
expect(result).toBe(true);
|
|
229
|
+
expect(mockedExecSync).toHaveBeenCalledWith(
|
|
230
|
+
`tasklist /FI "PID eq ${testPid}" /FO CSV /NH`,
|
|
231
|
+
{
|
|
232
|
+
encoding: "utf8",
|
|
233
|
+
timeout: 3000,
|
|
234
|
+
}
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
Object.defineProperty(process, "platform", {
|
|
238
|
+
value: originalPlatform,
|
|
239
|
+
writable: true,
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it("应在类Unix系统上检查进程命令行", () => {
|
|
244
|
+
const originalPlatform = process.platform;
|
|
245
|
+
Object.defineProperty(process, "platform", {
|
|
246
|
+
value: "linux",
|
|
247
|
+
writable: true,
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
vi.stubEnv("XIAOZHI_CONTAINER", "false");
|
|
251
|
+
vi.stubEnv("NODE_ENV", "production");
|
|
252
|
+
mockedExecSync.mockReturnValue("node\n");
|
|
253
|
+
|
|
254
|
+
const result = PlatformUtils.isXiaozhiProcess(testPid);
|
|
255
|
+
|
|
256
|
+
expect(result).toBe(true);
|
|
257
|
+
expect(mockedExecSync).toHaveBeenCalledWith(`ps -p ${testPid} -o comm=`, {
|
|
258
|
+
encoding: "utf8",
|
|
259
|
+
timeout: 3000,
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
Object.defineProperty(process, "platform", {
|
|
263
|
+
value: originalPlatform,
|
|
264
|
+
writable: true,
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it("命令行检查失败时应回退到简单PID检查", () => {
|
|
269
|
+
const originalPlatform = process.platform;
|
|
270
|
+
Object.defineProperty(process, "platform", {
|
|
271
|
+
value: "linux",
|
|
272
|
+
writable: true,
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
vi.stubEnv("XIAOZHI_CONTAINER", "false");
|
|
276
|
+
vi.stubEnv("NODE_ENV", "production");
|
|
277
|
+
mockedExecSync.mockImplementation(() => {
|
|
278
|
+
throw new Error("Command failed");
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
const result = PlatformUtils.isXiaozhiProcess(testPid);
|
|
282
|
+
|
|
283
|
+
expect(result).toBe(true);
|
|
284
|
+
expect(process.kill).toHaveBeenCalledWith(testPid, 0);
|
|
285
|
+
|
|
286
|
+
Object.defineProperty(process, "platform", {
|
|
287
|
+
value: originalPlatform,
|
|
288
|
+
writable: true,
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it("进程不存在时应返回 false", () => {
|
|
293
|
+
vi.stubEnv("XIAOZHI_CONTAINER", "false");
|
|
294
|
+
vi.stubEnv("NODE_ENV", "production");
|
|
295
|
+
vi.spyOn(process, "kill").mockImplementation(() => {
|
|
296
|
+
throw new Error("Process not found");
|
|
297
|
+
});
|
|
298
|
+
mockedExecSync.mockImplementation(() => {
|
|
299
|
+
throw new Error("Command failed");
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
const result = PlatformUtils.isXiaozhiProcess(testPid);
|
|
303
|
+
|
|
304
|
+
expect(result).toBe(false);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it("应在进程名中检测到小智", () => {
|
|
308
|
+
const originalPlatform = process.platform;
|
|
309
|
+
Object.defineProperty(process, "platform", {
|
|
310
|
+
value: "linux",
|
|
311
|
+
writable: true,
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
vi.stubEnv("XIAOZHI_CONTAINER", "false");
|
|
315
|
+
vi.stubEnv("NODE_ENV", "production");
|
|
316
|
+
mockedExecSync.mockReturnValue("xiaozhi\n");
|
|
317
|
+
|
|
318
|
+
const result = PlatformUtils.isXiaozhiProcess(testPid);
|
|
319
|
+
|
|
320
|
+
expect(result).toBe(true);
|
|
321
|
+
|
|
322
|
+
Object.defineProperty(process, "platform", {
|
|
323
|
+
value: originalPlatform,
|
|
324
|
+
writable: true,
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it("对于非小智进程应返回 false", () => {
|
|
329
|
+
const originalPlatform = process.platform;
|
|
330
|
+
Object.defineProperty(process, "platform", {
|
|
331
|
+
value: "linux",
|
|
332
|
+
writable: true,
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
vi.stubEnv("XIAOZHI_CONTAINER", "false");
|
|
336
|
+
vi.stubEnv("NODE_ENV", "production");
|
|
337
|
+
mockedExecSync.mockReturnValue("other-process\n");
|
|
338
|
+
|
|
339
|
+
const result = PlatformUtils.isXiaozhiProcess(testPid);
|
|
340
|
+
|
|
341
|
+
expect(result).toBe(false);
|
|
342
|
+
|
|
343
|
+
Object.defineProperty(process, "platform", {
|
|
344
|
+
value: originalPlatform,
|
|
345
|
+
writable: true,
|
|
346
|
+
});
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
describe("终止进程", () => {
|
|
351
|
+
const testPid = 1234;
|
|
352
|
+
|
|
353
|
+
beforeEach(() => {
|
|
354
|
+
vi.useFakeTimers();
|
|
355
|
+
vi.spyOn(process, "kill").mockImplementation(() => true);
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
afterEach(() => {
|
|
359
|
+
vi.useRealTimers();
|
|
360
|
+
vi.restoreAllMocks();
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
it("应使用SIGTERM成功终止进程", async () => {
|
|
364
|
+
// First call succeeds, second call fails (process stopped)
|
|
365
|
+
vi.spyOn(process, "kill")
|
|
366
|
+
.mockImplementationOnce(() => true)
|
|
367
|
+
.mockImplementationOnce(() => {
|
|
368
|
+
throw new Error("Process not found");
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
const promise = PlatformUtils.killProcess(testPid, "SIGTERM");
|
|
372
|
+
|
|
373
|
+
// 推进时间让进程停止检查完成
|
|
374
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
375
|
+
|
|
376
|
+
await promise;
|
|
377
|
+
|
|
378
|
+
expect(process.kill).toHaveBeenCalledWith(testPid, "SIGTERM");
|
|
379
|
+
expect(process.kill).toHaveBeenCalledWith(testPid, 0);
|
|
380
|
+
}, 1000);
|
|
381
|
+
|
|
382
|
+
it("如果进程不停止应使用SIGKILL强制终止", async () => {
|
|
383
|
+
// Process keeps running for all 30 checks, then gets SIGKILLed
|
|
384
|
+
let callCount = 0;
|
|
385
|
+
vi.spyOn(process, "kill").mockImplementation((pid, signal) => {
|
|
386
|
+
callCount++;
|
|
387
|
+
|
|
388
|
+
// 第一次调用:发送 SIGTERM
|
|
389
|
+
if (callCount === 1) {
|
|
390
|
+
return true;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// 第2-32次调用:检查进程是否还在(信号 0)
|
|
394
|
+
// 让进程在31次检查后仍然"存活"(30次循环检查 + 1次SIGKILL前的检查)
|
|
395
|
+
if (signal === 0 && callCount <= 32) {
|
|
396
|
+
return true; // 进程仍然存活
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// 第33次调用:发送 SIGKILL
|
|
400
|
+
if (signal === "SIGKILL") {
|
|
401
|
+
return true;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// SIGKILL 后的检查,进程已经停止(如果有)
|
|
405
|
+
if (signal === 0 && callCount > 32) {
|
|
406
|
+
throw new Error("Process not found");
|
|
407
|
+
}
|
|
408
|
+
return true;
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
const promise = PlatformUtils.killProcess(testPid, "SIGTERM");
|
|
412
|
+
|
|
413
|
+
// 推进时间让完整的进程停止流程完成
|
|
414
|
+
await vi.advanceTimersByTimeAsync(3500); // 30 * 100ms + 500ms + 余量
|
|
415
|
+
|
|
416
|
+
await promise;
|
|
417
|
+
|
|
418
|
+
// Should try SIGTERM first, then check multiple times, then SIGKILL
|
|
419
|
+
expect(process.kill).toHaveBeenCalledWith(testPid, "SIGTERM");
|
|
420
|
+
expect(process.kill).toHaveBeenCalledWith(testPid, "SIGKILL");
|
|
421
|
+
}, 1000);
|
|
422
|
+
|
|
423
|
+
it("未指定时应使用默认信号SIGTERM", async () => {
|
|
424
|
+
vi.spyOn(process, "kill")
|
|
425
|
+
.mockImplementationOnce(() => true)
|
|
426
|
+
.mockImplementationOnce(() => {
|
|
427
|
+
throw new Error("Process not found");
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
const promise = PlatformUtils.killProcess(testPid);
|
|
431
|
+
|
|
432
|
+
// 推进时间让进程停止检查完成
|
|
433
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
434
|
+
|
|
435
|
+
await promise;
|
|
436
|
+
|
|
437
|
+
expect(process.kill).toHaveBeenCalledWith(testPid, "SIGTERM");
|
|
438
|
+
}, 1000);
|
|
439
|
+
|
|
440
|
+
it("终止失败时应抛出进程错误", async () => {
|
|
441
|
+
vi.spyOn(process, "kill").mockImplementation(() => {
|
|
442
|
+
throw new Error("Permission denied");
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
await expect(PlatformUtils.killProcess(testPid)).rejects.toThrow(
|
|
446
|
+
ProcessError
|
|
447
|
+
);
|
|
448
|
+
await expect(PlatformUtils.killProcess(testPid)).rejects.toThrow(
|
|
449
|
+
"无法终止进程"
|
|
450
|
+
);
|
|
451
|
+
}, 1000);
|
|
452
|
+
|
|
453
|
+
it("应优雅处理SIGKILL失败", async () => {
|
|
454
|
+
// Process stops responding to SIGKILL
|
|
455
|
+
vi.spyOn(process, "kill").mockImplementation(() => true);
|
|
456
|
+
|
|
457
|
+
const promise = PlatformUtils.killProcess(testPid, "SIGTERM");
|
|
458
|
+
|
|
459
|
+
// 推进时间让完整的kill流程完成(包括SIGKILL尝试)
|
|
460
|
+
await vi.advanceTimersByTimeAsync(3500); // 足够时间完成整个流程
|
|
461
|
+
|
|
462
|
+
await promise;
|
|
463
|
+
|
|
464
|
+
// Should not throw even if SIGKILL fails
|
|
465
|
+
expect(process.kill).toHaveBeenCalledWith(testPid, "SIGKILL");
|
|
466
|
+
}, 1000);
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
describe("进程是否存在", () => {
|
|
470
|
+
const testPid = 1234;
|
|
471
|
+
|
|
472
|
+
beforeEach(() => {
|
|
473
|
+
vi.spyOn(process, "kill").mockImplementation(() => true);
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
afterEach(() => {
|
|
477
|
+
vi.restoreAllMocks();
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
it("进程存在时应返回 true", () => {
|
|
481
|
+
const result = PlatformUtils.processExists(testPid);
|
|
482
|
+
|
|
483
|
+
expect(result).toBe(true);
|
|
484
|
+
expect(process.kill).toHaveBeenCalledWith(testPid, 0);
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
it("进程不存在时应返回 false", () => {
|
|
488
|
+
vi.spyOn(process, "kill").mockImplementation(() => {
|
|
489
|
+
throw new Error("Process not found");
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
const result = PlatformUtils.processExists(testPid);
|
|
493
|
+
|
|
494
|
+
expect(result).toBe(false);
|
|
495
|
+
});
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
describe("获取系统信息", () => {
|
|
499
|
+
beforeEach(() => {
|
|
500
|
+
// Mock platform, arch, and version
|
|
501
|
+
const originalPlatform = process.platform;
|
|
502
|
+
const originalArch = process.arch;
|
|
503
|
+
const originalVersion = process.version;
|
|
504
|
+
|
|
505
|
+
Object.defineProperty(process, "platform", {
|
|
506
|
+
value: "darwin",
|
|
507
|
+
writable: true,
|
|
508
|
+
});
|
|
509
|
+
Object.defineProperty(process, "arch", {
|
|
510
|
+
value: "x64",
|
|
511
|
+
writable: true,
|
|
512
|
+
});
|
|
513
|
+
Object.defineProperty(process, "version", {
|
|
514
|
+
value: "v18.0.0",
|
|
515
|
+
writable: true,
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
return () => {
|
|
519
|
+
Object.defineProperty(process, "platform", {
|
|
520
|
+
value: originalPlatform,
|
|
521
|
+
writable: true,
|
|
522
|
+
});
|
|
523
|
+
Object.defineProperty(process, "arch", {
|
|
524
|
+
value: originalArch,
|
|
525
|
+
writable: true,
|
|
526
|
+
});
|
|
527
|
+
Object.defineProperty(process, "version", {
|
|
528
|
+
value: originalVersion,
|
|
529
|
+
writable: true,
|
|
530
|
+
});
|
|
531
|
+
};
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
it("应返回正确的系统信息", () => {
|
|
535
|
+
vi.stubEnv("XIAOZHI_CONTAINER", "true");
|
|
536
|
+
|
|
537
|
+
const result = PlatformUtils.getSystemInfo();
|
|
538
|
+
|
|
539
|
+
expect(result).toEqual({
|
|
540
|
+
platform: "darwin",
|
|
541
|
+
arch: "x64",
|
|
542
|
+
nodeVersion: "v18.0.0",
|
|
543
|
+
isContainer: true,
|
|
544
|
+
});
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
it("应检测非容器环境", () => {
|
|
548
|
+
vi.stubEnv("XIAOZHI_CONTAINER", "false");
|
|
549
|
+
|
|
550
|
+
const result = PlatformUtils.getSystemInfo();
|
|
551
|
+
|
|
552
|
+
expect(result.isContainer).toBe(false);
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
it("应处理缺失的容器环境变量", () => {
|
|
556
|
+
vi.stubEnv("XIAOZHI_CONTAINER", undefined);
|
|
557
|
+
|
|
558
|
+
const result = PlatformUtils.getSystemInfo();
|
|
559
|
+
|
|
560
|
+
expect(result.isContainer).toBe(false);
|
|
561
|
+
});
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
describe("获取环境变量", () => {
|
|
565
|
+
beforeEach(() => {
|
|
566
|
+
vi.stubEnv("TEST_VAR", "test-value");
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
afterEach(() => {
|
|
570
|
+
vi.unstubAllEnvs();
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
it("应返回环境变量值", () => {
|
|
574
|
+
const result = PlatformUtils.getEnvVar("TEST_VAR");
|
|
575
|
+
expect(result).toBe("test-value");
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
it("环境变量不存在时应返回 undefined", () => {
|
|
579
|
+
const result = PlatformUtils.getEnvVar("NONEXISTENT_VAR");
|
|
580
|
+
expect(result).toBeUndefined();
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
it("环境变量不存在时应返回默认值", () => {
|
|
584
|
+
const result = PlatformUtils.getEnvVar("NONEXISTENT_VAR", "default");
|
|
585
|
+
expect(result).toBe("default");
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
it("即使有默认值,环境变量存在时也应返回实际值", () => {
|
|
589
|
+
const result = PlatformUtils.getEnvVar("TEST_VAR", "default");
|
|
590
|
+
expect(result).toBe("test-value");
|
|
591
|
+
});
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
describe("设置环境变量", () => {
|
|
595
|
+
afterEach(() => {
|
|
596
|
+
vi.unstubAllEnvs();
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
it("应设置环境变量", () => {
|
|
600
|
+
PlatformUtils.setEnvVar("TEST_VAR", "new-value");
|
|
601
|
+
expect(process.env.TEST_VAR).toBe("new-value");
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
it("应覆盖现有环境变量", () => {
|
|
605
|
+
vi.stubEnv("TEST_VAR", "old-value");
|
|
606
|
+
PlatformUtils.setEnvVar("TEST_VAR", "new-value");
|
|
607
|
+
expect(process.env.TEST_VAR).toBe("new-value");
|
|
608
|
+
});
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
describe("是否为容器环境", () => {
|
|
612
|
+
it("在容器环境中应返回 true", () => {
|
|
613
|
+
vi.stubEnv("XIAOZHI_CONTAINER", "true");
|
|
614
|
+
expect(PlatformUtils.isContainerEnvironment()).toBe(true);
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
it("不在容器环境中时应返回 false", () => {
|
|
618
|
+
vi.stubEnv("XIAOZHI_CONTAINER", "false");
|
|
619
|
+
expect(PlatformUtils.isContainerEnvironment()).toBe(false);
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
it("未设置容器环境变量时应返回 false", () => {
|
|
623
|
+
vi.stubEnv("XIAOZHI_CONTAINER", undefined);
|
|
624
|
+
expect(PlatformUtils.isContainerEnvironment()).toBe(false);
|
|
625
|
+
});
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
describe("是否为测试环境", () => {
|
|
629
|
+
it("在测试环境中应返回 true", () => {
|
|
630
|
+
vi.stubEnv("NODE_ENV", "test");
|
|
631
|
+
expect(PlatformUtils.isTestEnvironment()).toBe(true);
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
it("不在测试环境中时应返回 false", () => {
|
|
635
|
+
vi.stubEnv("NODE_ENV", "development");
|
|
636
|
+
expect(PlatformUtils.isTestEnvironment()).toBe(false);
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
it("测试环境未设置NODE_ENV时应返回 false", () => {
|
|
640
|
+
vi.stubEnv("NODE_ENV", undefined);
|
|
641
|
+
expect(PlatformUtils.isTestEnvironment()).toBe(false);
|
|
642
|
+
});
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
describe("是否为开发环境", () => {
|
|
646
|
+
it("在开发环境中应返回 true", () => {
|
|
647
|
+
vi.stubEnv("NODE_ENV", "development");
|
|
648
|
+
expect(PlatformUtils.isDevelopmentEnvironment()).toBe(true);
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
it("不在开发环境中时应返回 false", () => {
|
|
652
|
+
vi.stubEnv("NODE_ENV", "production");
|
|
653
|
+
expect(PlatformUtils.isDevelopmentEnvironment()).toBe(false);
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
it("开发环境未设置NODE_ENV时应返回 false", () => {
|
|
657
|
+
vi.stubEnv("NODE_ENV", undefined);
|
|
658
|
+
expect(PlatformUtils.isDevelopmentEnvironment()).toBe(false);
|
|
659
|
+
});
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
describe("获取tail命令", () => {
|
|
663
|
+
const testFile = "/path/to/log.txt";
|
|
664
|
+
|
|
665
|
+
it("在Windows上应返回powershell命令", () => {
|
|
666
|
+
const originalPlatform = process.platform;
|
|
667
|
+
Object.defineProperty(process, "platform", {
|
|
668
|
+
value: "win32",
|
|
669
|
+
writable: true,
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
const result = PlatformUtils.getTailCommand(testFile);
|
|
673
|
+
|
|
674
|
+
expect(result).toEqual({
|
|
675
|
+
command: "powershell",
|
|
676
|
+
args: ["-Command", `Get-Content -Path "${testFile}" -Wait`],
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
Object.defineProperty(process, "platform", {
|
|
680
|
+
value: originalPlatform,
|
|
681
|
+
writable: true,
|
|
682
|
+
});
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
it("在类Unix系统上应返回tail命令", () => {
|
|
686
|
+
const originalPlatform = process.platform;
|
|
687
|
+
Object.defineProperty(process, "platform", {
|
|
688
|
+
value: "darwin",
|
|
689
|
+
writable: true,
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
const result = PlatformUtils.getTailCommand(testFile);
|
|
693
|
+
|
|
694
|
+
expect(result).toEqual({
|
|
695
|
+
command: "tail",
|
|
696
|
+
args: ["-f", testFile],
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
Object.defineProperty(process, "platform", {
|
|
700
|
+
value: originalPlatform,
|
|
701
|
+
writable: true,
|
|
702
|
+
});
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
it("应处理文件路径中的特殊字符", () => {
|
|
706
|
+
const originalPlatform = process.platform;
|
|
707
|
+
Object.defineProperty(process, "platform", {
|
|
708
|
+
value: "win32",
|
|
709
|
+
writable: true,
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
const fileWithSpaces = "/path/with spaces/log.txt";
|
|
713
|
+
const result = PlatformUtils.getTailCommand(fileWithSpaces);
|
|
714
|
+
|
|
715
|
+
expect(result.args[1]).toContain(fileWithSpaces);
|
|
716
|
+
|
|
717
|
+
Object.defineProperty(process, "platform", {
|
|
718
|
+
value: originalPlatform,
|
|
719
|
+
writable: true,
|
|
720
|
+
});
|
|
721
|
+
});
|
|
722
|
+
});
|
|
723
|
+
});
|