@xiaozhi-client/cli 1.9.4-beta.10

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 (55) hide show
  1. package/README.md +98 -0
  2. package/package.json +31 -0
  3. package/project.json +75 -0
  4. package/src/Constants.ts +105 -0
  5. package/src/Container.ts +212 -0
  6. package/src/Types.ts +79 -0
  7. package/src/commands/CommandHandlerFactory.ts +98 -0
  8. package/src/commands/ConfigCommandHandler.ts +279 -0
  9. package/src/commands/EndpointCommandHandler.ts +158 -0
  10. package/src/commands/McpCommandHandler.ts +778 -0
  11. package/src/commands/ProjectCommandHandler.ts +254 -0
  12. package/src/commands/ServiceCommandHandler.ts +182 -0
  13. package/src/commands/__tests__/CommandHandlerFactory.test.ts +323 -0
  14. package/src/commands/__tests__/CommandRegistry.test.ts +287 -0
  15. package/src/commands/__tests__/ConfigCommandHandler.test.ts +844 -0
  16. package/src/commands/__tests__/EndpointCommandHandler.test.ts +426 -0
  17. package/src/commands/__tests__/McpCommandHandler.test.ts +753 -0
  18. package/src/commands/__tests__/ProjectCommandHandler.test.ts +230 -0
  19. package/src/commands/__tests__/ServiceCommands.integration.test.ts +408 -0
  20. package/src/commands/index.ts +351 -0
  21. package/src/errors/ErrorHandlers.ts +141 -0
  22. package/src/errors/ErrorMessages.ts +121 -0
  23. package/src/errors/__tests__/index.test.ts +186 -0
  24. package/src/errors/index.ts +163 -0
  25. package/src/global.d.ts +19 -0
  26. package/src/index.ts +53 -0
  27. package/src/interfaces/Command.ts +128 -0
  28. package/src/interfaces/CommandTypes.ts +95 -0
  29. package/src/interfaces/Config.ts +25 -0
  30. package/src/interfaces/Service.ts +99 -0
  31. package/src/services/DaemonManager.ts +318 -0
  32. package/src/services/ProcessManager.ts +235 -0
  33. package/src/services/ServiceManager.ts +319 -0
  34. package/src/services/TemplateManager.ts +382 -0
  35. package/src/services/__tests__/DaemonManager.test.ts +378 -0
  36. package/src/services/__tests__/DaemonMode.integration.test.ts +321 -0
  37. package/src/services/__tests__/ProcessManager.test.ts +296 -0
  38. package/src/services/__tests__/ServiceManager.test.ts +774 -0
  39. package/src/services/__tests__/TemplateManager.test.ts +337 -0
  40. package/src/types/backend.d.ts +48 -0
  41. package/src/utils/FileUtils.ts +320 -0
  42. package/src/utils/FormatUtils.ts +198 -0
  43. package/src/utils/PathUtils.ts +255 -0
  44. package/src/utils/PlatformUtils.ts +217 -0
  45. package/src/utils/Validation.ts +274 -0
  46. package/src/utils/VersionUtils.ts +141 -0
  47. package/src/utils/__tests__/FileUtils.test.ts +728 -0
  48. package/src/utils/__tests__/FormatUtils.test.ts +243 -0
  49. package/src/utils/__tests__/PathUtils.test.ts +1165 -0
  50. package/src/utils/__tests__/PlatformUtils.test.ts +723 -0
  51. package/src/utils/__tests__/Validation.test.ts +560 -0
  52. package/src/utils/__tests__/VersionUtils.test.ts +410 -0
  53. package/tsconfig.json +32 -0
  54. package/tsup.config.ts +100 -0
  55. package/vitest.config.ts +97 -0
@@ -0,0 +1,1165 @@
1
+ import { realpathSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { PathUtils } from "@cli/utils/PathUtils.js";
6
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
7
+
8
+ // Mock dependencies - 需要使用与源文件相同的导入方式
9
+ vi.mock("@cli/utils/FileUtils.js", () => ({
10
+ FileUtils: {
11
+ exists: vi.fn(),
12
+ },
13
+ }));
14
+
15
+ vi.mock("node:os", () => ({
16
+ tmpdir: vi.fn(),
17
+ }));
18
+
19
+ vi.mock("node:url", () => ({
20
+ fileURLToPath: vi.fn(),
21
+ }));
22
+
23
+ vi.mock("node:fs", () => ({
24
+ realpathSync: vi.fn(),
25
+ }));
26
+
27
+ // Mock process.argv and environment variables
28
+ const originalArgv = process.argv;
29
+ const originalEnv = process.env;
30
+
31
+ describe("PathUtils 路径工具", () => {
32
+ let mockFileExists: any;
33
+ let mockTmpdir: any;
34
+ let mockFileURLToPath: any;
35
+ let mockRealpathSync: any;
36
+
37
+ beforeEach(async () => {
38
+ vi.clearAllMocks();
39
+
40
+ // Setup mocks
41
+ const { FileUtils } = await import("@cli/utils/FileUtils.js");
42
+ mockFileExists = vi.mocked(FileUtils.exists);
43
+ mockTmpdir = vi.mocked(tmpdir);
44
+ mockFileURLToPath = vi.mocked(fileURLToPath);
45
+ mockRealpathSync = vi.mocked(realpathSync);
46
+
47
+ // Default mock implementations
48
+ mockFileExists.mockReturnValue(true);
49
+ mockTmpdir.mockReturnValue("/tmp");
50
+ mockFileURLToPath.mockReturnValue("/test/src/cli/utils/PathUtils.js");
51
+ mockRealpathSync.mockImplementation((path: string) => path); // 默认返回原路径
52
+
53
+ // Reset environment variables
54
+ process.env = { ...originalEnv };
55
+ });
56
+
57
+ afterEach(() => {
58
+ process.argv = originalArgv;
59
+ process.env = originalEnv;
60
+ });
61
+
62
+ describe("getExecutablePath 获取可执行文件路径", () => {
63
+ describe("基本路径解析", () => {
64
+ it("应该基于当前 CLI 脚本位置返回正确路径", () => {
65
+ // Mock process.argv[1] to simulate CLI script path
66
+ process.argv = ["node", "/Users/test/xiaozhi-client/dist/cli.js"];
67
+ mockRealpathSync.mockReturnValue(
68
+ "/Users/test/xiaozhi-client/dist/cli.js"
69
+ );
70
+
71
+ const result = PathUtils.getExecutablePath("WebServerLauncher");
72
+ const expected = path.join(
73
+ "/Users/test/xiaozhi-client/dist",
74
+ "WebServerLauncher.js"
75
+ );
76
+
77
+ expect(result).toBe(expected);
78
+ expect(mockRealpathSync).toHaveBeenCalledWith(
79
+ "/Users/test/xiaozhi-client/dist/cli.js"
80
+ );
81
+ });
82
+
83
+ it("应该处理不同的 CLI 脚本位置", () => {
84
+ process.argv = ["node", "/opt/xiaozhi/dist/cli.js"];
85
+ mockRealpathSync.mockReturnValue("/opt/xiaozhi/dist/cli.js");
86
+
87
+ const result = PathUtils.getExecutablePath("WebServerLauncher");
88
+ const expected = path.join("/opt/xiaozhi/dist", "WebServerLauncher.js");
89
+
90
+ expect(result).toBe(expected);
91
+ });
92
+
93
+ it("应该支持相对路径", () => {
94
+ process.argv = ["node", "./dist/cli.js"];
95
+ mockRealpathSync.mockReturnValue("./dist/cli.js");
96
+
97
+ const result = PathUtils.getExecutablePath("test");
98
+ const expected = path.join("./dist", "test.js");
99
+
100
+ expect(result).toBe(expected);
101
+ });
102
+ });
103
+
104
+ describe("符号链接解析测试", () => {
105
+ it("应该正确解析符号链接到真实路径", () => {
106
+ // 模拟 npm 全局安装的符号链接场景
107
+ const symlinkPath =
108
+ "/Users/nemo/.nvm/versions/node/v20.19.2/bin/xiaozhi";
109
+ const realPath =
110
+ "/Users/nemo/.nvm/versions/node/v20.19.2/lib/node_modules/xiaozhi-client/dist/cli.js";
111
+
112
+ process.argv = ["node", symlinkPath];
113
+ mockRealpathSync.mockReturnValue(realPath);
114
+
115
+ const result = PathUtils.getExecutablePath("WebServerLauncher");
116
+ const expected = path.join(
117
+ "/Users/nemo/.nvm/versions/node/v20.19.2/lib/node_modules/xiaozhi-client/dist",
118
+ "WebServerLauncher.js"
119
+ );
120
+
121
+ expect(result).toBe(expected);
122
+ expect(mockRealpathSync).toHaveBeenCalledWith(symlinkPath);
123
+ });
124
+
125
+ it("应该处理多层符号链接", () => {
126
+ const symlinkPath = "/usr/local/bin/xiaozhi";
127
+ const realPath = "/opt/xiaozhi-client/dist/cli.js";
128
+
129
+ process.argv = ["node", symlinkPath];
130
+ mockRealpathSync.mockReturnValue(realPath);
131
+
132
+ const result = PathUtils.getExecutablePath("WebServerLauncher");
133
+ const expected = path.join(
134
+ "/opt/xiaozhi-client/dist",
135
+ "WebServerLauncher.js"
136
+ );
137
+
138
+ expect(result).toBe(expected);
139
+ });
140
+
141
+ it("应该在符号链接解析失败时回退到原路径", () => {
142
+ const symlinkPath = "/broken/symlink/xiaozhi";
143
+
144
+ process.argv = ["node", symlinkPath];
145
+ mockRealpathSync.mockImplementation(() => {
146
+ throw new Error("ENOENT: no such file or directory");
147
+ });
148
+
149
+ const result = PathUtils.getExecutablePath("WebServerLauncher");
150
+ const expected = path.join("/broken/symlink", "WebServerLauncher.js");
151
+
152
+ expect(result).toBe(expected);
153
+ expect(mockRealpathSync).toHaveBeenCalledWith(symlinkPath);
154
+ });
155
+
156
+ it("应该处理权限错误导致的符号链接解析失败", () => {
157
+ const symlinkPath = "/restricted/xiaozhi";
158
+
159
+ process.argv = ["node", symlinkPath];
160
+ mockRealpathSync.mockImplementation(() => {
161
+ throw new Error("EACCES: permission denied");
162
+ });
163
+
164
+ const result = PathUtils.getExecutablePath("test");
165
+ const expected = path.join("/restricted", "test.js");
166
+
167
+ expect(result).toBe(expected);
168
+ });
169
+ });
170
+
171
+ describe("路径构建测试", () => {
172
+ it("应该正确构建不同可执行文件名称的路径", () => {
173
+ process.argv = ["node", "/test/dist/cli.js"];
174
+ mockRealpathSync.mockReturnValue("/test/dist/cli.js");
175
+
176
+ const testCases = [
177
+ { name: "WebServerLauncher", expected: "WebServerLauncher.js" },
178
+ { name: "customScript", expected: "customScript.js" },
179
+ { name: "app", expected: "app.js" },
180
+ ];
181
+
182
+ for (const { name, expected } of testCases) {
183
+ const result = PathUtils.getExecutablePath(name);
184
+ expect(result).toBe(path.join("/test/dist", expected));
185
+ }
186
+ });
187
+
188
+ it("应该确保返回的路径以 .js 结尾", () => {
189
+ process.argv = ["node", "/test/dist/cli.js"];
190
+ mockRealpathSync.mockReturnValue("/test/dist/cli.js");
191
+
192
+ const testNames = ["script", "app.exe", "tool.bin", "service"];
193
+
194
+ for (const name of testNames) {
195
+ const result = PathUtils.getExecutablePath(name);
196
+ expect(result).toMatch(/\.js$/);
197
+ expect(result).toContain(name);
198
+ }
199
+ });
200
+ });
201
+
202
+ describe("边界情况测试", () => {
203
+ it("应该处理空的可执行文件名", () => {
204
+ process.argv = ["node", "/test/dist/cli.js"];
205
+ mockRealpathSync.mockReturnValue("/test/dist/cli.js");
206
+
207
+ const result = PathUtils.getExecutablePath("");
208
+ const expected = path.join("/test/dist", ".js");
209
+
210
+ expect(result).toBe(expected);
211
+ });
212
+
213
+ it("应该处理 process.argv[1] 为 undefined 的情况", () => {
214
+ process.argv = ["node"]; // 没有第二个参数
215
+
216
+ const result = PathUtils.getExecutablePath("test");
217
+ const expected = path.join(process.cwd(), "test.js");
218
+
219
+ expect(result).toBe(expected);
220
+ });
221
+
222
+ it("应该处理非常长的路径", () => {
223
+ const longPath = `/very/long/path/${"a".repeat(200)}/dist/cli.js`;
224
+ process.argv = ["node", longPath];
225
+ mockRealpathSync.mockReturnValue(longPath);
226
+
227
+ const result = PathUtils.getExecutablePath("test");
228
+
229
+ expect(result).toContain("test.js");
230
+ expect(result.length).toBeGreaterThan(200);
231
+ });
232
+
233
+ it("应该处理包含特殊字符的路径", () => {
234
+ const specialPath = "/path with spaces/special-chars_123/dist/cli.js";
235
+ process.argv = ["node", specialPath];
236
+ mockRealpathSync.mockReturnValue(specialPath);
237
+
238
+ const result = PathUtils.getExecutablePath("test");
239
+ const expected = path.join(
240
+ "/path with spaces/special-chars_123/dist",
241
+ "test.js"
242
+ );
243
+
244
+ expect(result).toBe(expected);
245
+ });
246
+ });
247
+
248
+ describe("集成测试", () => {
249
+ it("应该模拟真实的 npm 全局安装环境", () => {
250
+ // 模拟真实的 npm 全局安装路径结构
251
+ const npmBinPath =
252
+ "/Users/user/.nvm/versions/node/v18.17.0/bin/xiaozhi";
253
+ const npmRealPath =
254
+ "/Users/user/.nvm/versions/node/v18.17.0/lib/node_modules/xiaozhi-client/dist/cli.js";
255
+
256
+ process.argv = ["node", npmBinPath];
257
+ mockRealpathSync.mockReturnValue(npmRealPath);
258
+
259
+ const webServerPath = PathUtils.getExecutablePath("WebServerLauncher");
260
+
261
+ const expectedDir =
262
+ "/Users/user/.nvm/versions/node/v18.17.0/lib/node_modules/xiaozhi-client/dist";
263
+
264
+ expect(webServerPath).toBe(
265
+ path.join(expectedDir, "WebServerLauncher.js")
266
+ );
267
+ });
268
+
269
+ it("应该在 Unix 系统路径格式下正确工作", () => {
270
+ const symlinkPath = "/usr/local/bin/xiaozhi";
271
+ const realPath = "/opt/xiaozhi-client/dist/cli.js";
272
+ const expectedDir = "/opt/xiaozhi-client/dist";
273
+
274
+ process.argv = ["node", symlinkPath];
275
+ mockRealpathSync.mockReturnValue(realPath);
276
+
277
+ const result = PathUtils.getExecutablePath("test");
278
+ const expected = path.join(expectedDir, "test.js");
279
+
280
+ expect(result).toBe(expected);
281
+ expect(mockRealpathSync).toHaveBeenCalledWith(symlinkPath);
282
+ });
283
+
284
+ it("应该处理跨平台路径格式", () => {
285
+ // 测试不同平台的路径格式都能正确处理
286
+ const testCases = [
287
+ {
288
+ name: "Unix 风格路径",
289
+ symlinkPath: "/usr/local/bin/xiaozhi",
290
+ realPath: "/opt/xiaozhi-client/dist/cli.js",
291
+ },
292
+ {
293
+ name: "Windows 风格路径(使用正斜杠)",
294
+ symlinkPath: "C:/npm/xiaozhi",
295
+ realPath: "C:/npm/node_modules/xiaozhi-client/dist/cli.js",
296
+ },
297
+ ];
298
+
299
+ for (const { name, symlinkPath, realPath } of testCases) {
300
+ process.argv = ["node", symlinkPath];
301
+ mockRealpathSync.mockReturnValue(realPath);
302
+
303
+ const result = PathUtils.getExecutablePath("test");
304
+ const expectedDir = path.dirname(realPath);
305
+ const expected = path.join(expectedDir, "test.js");
306
+
307
+ expect(result).toBe(expected);
308
+ expect(mockRealpathSync).toHaveBeenCalledWith(symlinkPath);
309
+
310
+ // 重置 mock 为下一次测试
311
+ mockRealpathSync.mockReset();
312
+ }
313
+ });
314
+
315
+ it("应该确保路径解析的一致性", () => {
316
+ const symlinkPath = "/usr/bin/xiaozhi";
317
+ const realPath = "/home/user/xiaozhi-client/dist/cli.js";
318
+
319
+ process.argv = ["node", symlinkPath];
320
+ mockRealpathSync.mockReturnValue(realPath);
321
+
322
+ // 多次调用应该返回一致的结果
323
+ const results = [];
324
+ for (let i = 0; i < 5; i++) {
325
+ results.push(PathUtils.getExecutablePath("WebServerLauncher"));
326
+ }
327
+
328
+ const firstResult = results[0];
329
+ for (const result of results) {
330
+ expect(result).toBe(firstResult);
331
+ }
332
+
333
+ expect(mockRealpathSync).toHaveBeenCalledTimes(5);
334
+ });
335
+ });
336
+
337
+ describe("错误恢复测试", () => {
338
+ it("应该处理各种文件系统错误", () => {
339
+ const errorCases = [
340
+ new Error("ENOENT: no such file or directory"),
341
+ new Error("EACCES: permission denied"),
342
+ new Error("ELOOP: too many symbolic links encountered"),
343
+ new Error("ENAMETOOLONG: name too long"),
344
+ new Error("ENOTDIR: not a directory"),
345
+ ];
346
+
347
+ for (const error of errorCases) {
348
+ process.argv = ["node", "/test/path/cli.js"];
349
+ mockRealpathSync.mockImplementation(() => {
350
+ throw error;
351
+ });
352
+
353
+ const result = PathUtils.getExecutablePath("test");
354
+ const expected = path.join("/test/path", "test.js");
355
+
356
+ expect(result).toBe(expected);
357
+ }
358
+ });
359
+
360
+ it("应该在符号链接循环时回退到原路径", () => {
361
+ process.argv = ["node", "/circular/symlink/xiaozhi"];
362
+ mockRealpathSync.mockImplementation(() => {
363
+ throw new Error("ELOOP: too many symbolic links encountered");
364
+ });
365
+
366
+ const result = PathUtils.getExecutablePath("WebServerLauncher");
367
+ const expected = path.join("/circular/symlink", "WebServerLauncher.js");
368
+
369
+ expect(result).toBe(expected);
370
+ });
371
+ });
372
+ });
373
+
374
+ describe("getWebServerLauncherPath 获取 WebServer 启动器路径", () => {
375
+ it("应该返回正确的 WebServerLauncher 路径", () => {
376
+ process.argv = ["node", "/Users/test/xiaozhi-client/dist/cli.js"];
377
+
378
+ const result = PathUtils.getWebServerLauncherPath();
379
+ const expected = path.join(
380
+ "/Users/test/xiaozhi-client/dist",
381
+ "WebServerLauncher.js"
382
+ );
383
+
384
+ expect(result).toBe(expected);
385
+ });
386
+ });
387
+
388
+ describe("路径解析一致性", () => {
389
+ it("应该在不同方法间保持一致的路径解析", () => {
390
+ // 使用跨平台的路径格式
391
+ const testCliPath = path.join("/test", "dist", "cli.js");
392
+ process.argv = ["node", testCliPath];
393
+
394
+ const webServerPath = PathUtils.getWebServerLauncherPath();
395
+ const customPath = PathUtils.getExecutablePath("custom");
396
+
397
+ // All paths should be in the same directory
398
+ const webServerDir = path.dirname(webServerPath);
399
+ const customDir = path.dirname(customPath);
400
+
401
+ expect(webServerDir).toBe(customDir);
402
+ // 使用跨平台的期望路径
403
+ const expectedDir = path.join("/test", "dist");
404
+ expect(customDir).toBe(expectedDir);
405
+ });
406
+ });
407
+
408
+ describe("边界情况", () => {
409
+ it("应该处理空的可执行文件名", () => {
410
+ process.argv = ["node", "/test/dist/cli.js"];
411
+
412
+ const result = PathUtils.getExecutablePath("");
413
+ const expected = path.join("/test/dist", ".js");
414
+
415
+ expect(result).toBe(expected);
416
+ });
417
+
418
+ it("应该处理带扩展名的可执行文件名", () => {
419
+ process.argv = ["node", "/test/dist/cli.js"];
420
+
421
+ const result = PathUtils.getExecutablePath("test.exe");
422
+ const expected = path.join("/test/dist", "test.exe.js");
423
+
424
+ expect(result).toBe(expected);
425
+ });
426
+
427
+ it("应该处理复杂的目录结构", () => {
428
+ process.argv = [
429
+ "node",
430
+ "/very/deep/nested/directory/structure/dist/cli.js",
431
+ ];
432
+
433
+ const result = PathUtils.getExecutablePath("app");
434
+ const expected = path.join(
435
+ "/very/deep/nested/directory/structure/dist",
436
+ "app.js"
437
+ );
438
+
439
+ expect(result).toBe(expected);
440
+ });
441
+ });
442
+
443
+ describe("getPidFile 获取 PID 文件路径", () => {
444
+ it("应该使用环境变量中的配置目录", () => {
445
+ process.env.XIAOZHI_CONFIG_DIR = "/custom/config";
446
+
447
+ const result = PathUtils.getPidFile();
448
+ const expected = path.join("/custom/config", ".xiaozhi-mcp-service.pid");
449
+
450
+ expect(result).toBe(expected);
451
+ });
452
+
453
+ it("应该在没有环境变量时使用当前工作目录", () => {
454
+ process.env.XIAOZHI_CONFIG_DIR = undefined;
455
+ const originalCwd = process.cwd();
456
+
457
+ const result = PathUtils.getPidFile();
458
+ const expected = path.join(originalCwd, ".xiaozhi-mcp-service.pid");
459
+
460
+ expect(result).toBe(expected);
461
+ });
462
+
463
+ it("应该处理空的环境变量", () => {
464
+ process.env.XIAOZHI_CONFIG_DIR = "";
465
+ const originalCwd = process.cwd();
466
+
467
+ const result = PathUtils.getPidFile();
468
+ const expected = path.join(originalCwd, ".xiaozhi-mcp-service.pid");
469
+
470
+ expect(result).toBe(expected);
471
+ });
472
+ });
473
+
474
+ describe("getLogFile 获取日志文件路径", () => {
475
+ it("应该使用提供的项目目录", () => {
476
+ const projectDir = "/custom/project";
477
+
478
+ const result = PathUtils.getLogFile(projectDir);
479
+ const expected = path.join(projectDir, "xiaozhi.log");
480
+
481
+ expect(result).toBe(expected);
482
+ });
483
+
484
+ it("应该在没有项目目录时使用当前工作目录", () => {
485
+ const originalCwd = process.cwd();
486
+
487
+ const result = PathUtils.getLogFile();
488
+ const expected = path.join(originalCwd, "xiaozhi.log");
489
+
490
+ expect(result).toBe(expected);
491
+ });
492
+
493
+ it("应该处理空字符串项目目录", () => {
494
+ const originalCwd = process.cwd();
495
+
496
+ const result = PathUtils.getLogFile("");
497
+ const expected = path.join(originalCwd, "xiaozhi.log");
498
+
499
+ expect(result).toBe(expected);
500
+ });
501
+ });
502
+
503
+ describe("getConfigDir 获取配置目录路径", () => {
504
+ it("应该返回环境变量中的配置目录", () => {
505
+ process.env.XIAOZHI_CONFIG_DIR = "/env/config";
506
+
507
+ const result = PathUtils.getConfigDir();
508
+
509
+ expect(result).toBe("/env/config");
510
+ });
511
+
512
+ it("应该在没有环境变量时返回当前工作目录", () => {
513
+ process.env.XIAOZHI_CONFIG_DIR = undefined;
514
+ const originalCwd = process.cwd();
515
+
516
+ const result = PathUtils.getConfigDir();
517
+
518
+ expect(result).toBe(originalCwd);
519
+ });
520
+ });
521
+
522
+ describe("getWorkDir 获取工作目录路径", () => {
523
+ it("应该基于配置目录返回工作目录", () => {
524
+ process.env.XIAOZHI_CONFIG_DIR = "/custom/config";
525
+
526
+ const result = PathUtils.getWorkDir();
527
+ const expected = path.join("/custom/config", ".xiaozhi");
528
+
529
+ expect(result).toBe(expected);
530
+ });
531
+
532
+ it("应该在没有配置目录时基于当前工作目录", () => {
533
+ process.env.XIAOZHI_CONFIG_DIR = undefined;
534
+ const originalCwd = process.cwd();
535
+
536
+ const result = PathUtils.getWorkDir();
537
+ const expected = path.join(originalCwd, ".xiaozhi");
538
+
539
+ expect(result).toBe(expected);
540
+ });
541
+ });
542
+
543
+ describe("getTemplatesDir 获取模板目录路径", () => {
544
+ it("应该返回所有可能的模板目录路径", () => {
545
+ mockFileURLToPath.mockReturnValue("/test/src/cli/utils/PathUtils.js");
546
+
547
+ const result = PathUtils.getTemplatesDir();
548
+
549
+ expect(result).toHaveLength(3);
550
+ expect(result[0]).toContain("templates");
551
+ expect(result[1]).toContain("templates");
552
+ expect(result[2]).toContain("templates");
553
+ });
554
+
555
+ it("应该处理不同的脚本位置", () => {
556
+ mockFileURLToPath.mockReturnValue(
557
+ "/different/path/cli/utils/PathUtils.js"
558
+ );
559
+
560
+ const result = PathUtils.getTemplatesDir();
561
+
562
+ expect(result).toHaveLength(3);
563
+ expect(result[0]).toBe(
564
+ path.join("/different/path/cli/utils", "templates")
565
+ );
566
+ });
567
+ });
568
+
569
+ describe("findTemplatesDir 查找模板目录", () => {
570
+ it("应该返回第一个存在的模板目录", () => {
571
+ mockFileExists
572
+ .mockReturnValueOnce(false) // 第一个不存在
573
+ .mockReturnValueOnce(true); // 第二个存在
574
+
575
+ const result = PathUtils.findTemplatesDir();
576
+
577
+ expect(result).not.toBeNull();
578
+ expect(mockFileExists).toHaveBeenCalledTimes(2);
579
+ });
580
+
581
+ it("应该在没有找到模板目录时返回 null", () => {
582
+ mockFileExists.mockReturnValue(false);
583
+
584
+ const result = PathUtils.findTemplatesDir();
585
+
586
+ expect(result).toBeNull();
587
+ expect(mockFileExists).toHaveBeenCalledTimes(3);
588
+ });
589
+
590
+ it("应该返回第一个匹配的目录", () => {
591
+ mockFileExists.mockReturnValue(true);
592
+
593
+ const result = PathUtils.findTemplatesDir();
594
+
595
+ expect(result).not.toBeNull();
596
+ expect(mockFileExists).toHaveBeenCalledTimes(1);
597
+ });
598
+ });
599
+
600
+ describe("getTemplatePath 获取模板路径", () => {
601
+ it("应该返回存在的模板路径", () => {
602
+ const templateName = "test-template";
603
+ mockFileExists
604
+ .mockReturnValueOnce(true) // findTemplatesDir 找到目录
605
+ .mockReturnValueOnce(true); // 模板文件存在
606
+
607
+ const result = PathUtils.getTemplatePath(templateName);
608
+
609
+ expect(result).not.toBeNull();
610
+ expect(result).toContain(templateName);
611
+ });
612
+
613
+ it("应该在模板目录不存在时返回 null", () => {
614
+ mockFileExists.mockReturnValue(false);
615
+
616
+ const result = PathUtils.getTemplatePath("test-template");
617
+
618
+ expect(result).toBeNull();
619
+ });
620
+
621
+ it("应该在模板文件不存在时返回 null", () => {
622
+ mockFileExists
623
+ .mockReturnValueOnce(true) // findTemplatesDir 找到目录
624
+ .mockReturnValueOnce(false); // 模板文件不存在
625
+
626
+ const result = PathUtils.getTemplatePath("non-existent-template");
627
+
628
+ expect(result).toBeNull();
629
+ });
630
+ });
631
+
632
+ describe("getScriptDir 获取脚本目录路径", () => {
633
+ it("应该返回脚本所在目录", () => {
634
+ mockFileURLToPath.mockReturnValue("/test/src/cli/utils/PathUtils.js");
635
+
636
+ const result = PathUtils.getScriptDir();
637
+
638
+ expect(result).toBe("/test/src/cli/utils");
639
+ expect(mockFileURLToPath).toHaveBeenCalledWith(expect.any(String));
640
+ });
641
+
642
+ it("应该处理不同的脚本路径", () => {
643
+ mockFileURLToPath.mockReturnValue("/different/path/script.js");
644
+
645
+ const result = PathUtils.getScriptDir();
646
+
647
+ expect(result).toBe("/different/path");
648
+ });
649
+ });
650
+
651
+ describe("getProjectRoot 获取项目根目录路径", () => {
652
+ it("应该从脚本目录计算项目根目录", () => {
653
+ mockFileURLToPath.mockReturnValue("/project/src/cli/utils/PathUtils.js");
654
+
655
+ const result = PathUtils.getProjectRoot();
656
+ // 使用跨平台的路径比较
657
+ const expected = path.normalize("/project");
658
+
659
+ expect(result).toBe(expected);
660
+ });
661
+
662
+ it("应该处理不同深度的脚本路径", () => {
663
+ mockFileURLToPath.mockReturnValue(
664
+ "/deep/nested/src/cli/utils/PathUtils.js"
665
+ );
666
+
667
+ const result = PathUtils.getProjectRoot();
668
+ // 使用跨平台的路径比较
669
+ const expected = path.normalize("/deep/nested");
670
+
671
+ expect(result).toBe(expected);
672
+ });
673
+ });
674
+
675
+ describe("getDistDir 获取构建输出目录路径", () => {
676
+ it("应该返回项目根目录下的 dist 目录", () => {
677
+ mockFileURLToPath.mockReturnValue("/project/src/cli/utils/PathUtils.js");
678
+
679
+ const result = PathUtils.getDistDir();
680
+ const expected = path.join("/project", "dist");
681
+
682
+ expect(result).toBe(expected);
683
+ });
684
+ });
685
+
686
+ describe("getRelativePath 获取相对路径", () => {
687
+ it("应该返回相对于项目根目录的路径", () => {
688
+ mockFileURLToPath.mockReturnValue("/project/src/cli/utils/PathUtils.js");
689
+ const filePath = "/project/src/test/file.js";
690
+
691
+ const result = PathUtils.getRelativePath(filePath);
692
+ const expected = path.relative("/project", filePath);
693
+
694
+ expect(result).toBe(expected);
695
+ });
696
+
697
+ it("应该处理项目外的文件路径", () => {
698
+ mockFileURLToPath.mockReturnValue("/project/src/cli/utils/PathUtils.js");
699
+ const filePath = "/outside/project/file.js";
700
+
701
+ const result = PathUtils.getRelativePath(filePath);
702
+
703
+ expect(result).toContain("..");
704
+ });
705
+ });
706
+
707
+ describe("resolveConfigPath 解析配置文件路径", () => {
708
+ it("应该返回指定格式的配置文件路径", () => {
709
+ process.env.XIAOZHI_CONFIG_DIR = "/config";
710
+
711
+ const result = PathUtils.resolveConfigPath("json5");
712
+ const expected = path.join("/config", "xiaozhi.config.json5");
713
+
714
+ expect(result).toBe(expected);
715
+ });
716
+
717
+ it("应该按优先级查找存在的配置文件", () => {
718
+ process.env.XIAOZHI_CONFIG_DIR = "/config";
719
+ mockFileExists
720
+ .mockReturnValueOnce(false) // json5 不存在
721
+ .mockReturnValueOnce(true); // jsonc 存在
722
+
723
+ const result = PathUtils.resolveConfigPath();
724
+ const expected = path.join("/config", "xiaozhi.config.jsonc");
725
+
726
+ expect(result).toBe(expected);
727
+ });
728
+
729
+ it("应该在没有找到配置文件时返回默认路径", () => {
730
+ process.env.XIAOZHI_CONFIG_DIR = "/config";
731
+ mockFileExists.mockReturnValue(false);
732
+
733
+ const result = PathUtils.resolveConfigPath();
734
+ const expected = path.join("/config", "xiaozhi.config.json");
735
+
736
+ expect(result).toBe(expected);
737
+ });
738
+
739
+ it("应该处理所有支持的配置文件格式", () => {
740
+ process.env.XIAOZHI_CONFIG_DIR = "/config";
741
+
742
+ const formats = ["json", "json5", "jsonc"] as const;
743
+
744
+ for (const format of formats) {
745
+ const result = PathUtils.resolveConfigPath(format);
746
+ const expected = path.join("/config", `xiaozhi.config.${format}`);
747
+ expect(result).toBe(expected);
748
+ }
749
+ });
750
+ });
751
+
752
+ describe("getDefaultConfigPath 获取默认配置文件路径", () => {
753
+ it("应该返回项目根目录下的默认配置文件路径", () => {
754
+ mockFileURLToPath.mockReturnValue("/project/src/cli/utils/PathUtils.js");
755
+
756
+ const result = PathUtils.getDefaultConfigPath();
757
+ const expected = path.join("/project", "xiaozhi.config.default.json");
758
+
759
+ expect(result).toBe(expected);
760
+ });
761
+ });
762
+
763
+ describe("validatePath 验证路径安全性", () => {
764
+ it("应该验证安全的路径", () => {
765
+ const safePaths = [
766
+ "normal/path/file.txt",
767
+ "./relative/path",
768
+ "file.txt",
769
+ "dir/subdir/file.ext",
770
+ ];
771
+
772
+ for (const safePath of safePaths) {
773
+ const result = PathUtils.validatePath(safePath);
774
+ expect(result).toBe(true);
775
+ }
776
+ });
777
+
778
+ it("应该拒绝包含路径遍历的路径", () => {
779
+ const unsafePaths = [
780
+ "../../../etc/passwd",
781
+ "dir/../../../secret",
782
+ "normal/../../../file",
783
+ "..\\..\\windows\\system32",
784
+ ];
785
+
786
+ for (const unsafePath of unsafePaths) {
787
+ const result = PathUtils.validatePath(unsafePath);
788
+ expect(result).toBe(false);
789
+ }
790
+ });
791
+
792
+ it("应该处理复杂的路径遍历尝试", () => {
793
+ const complexUnsafePaths = [
794
+ "dir/./../../file",
795
+ "normal/path/../../../../../../etc/passwd",
796
+ "./../config",
797
+ ];
798
+
799
+ for (const unsafePath of complexUnsafePaths) {
800
+ const result = PathUtils.validatePath(unsafePath);
801
+ expect(result).toBe(false);
802
+ }
803
+ });
804
+ });
805
+
806
+ describe("ensurePathWithin 确保路径在指定目录内", () => {
807
+ it("应该返回在基础目录内的安全路径", () => {
808
+ const baseDir = "/safe/base";
809
+ const inputPath = "subdir/file.txt";
810
+
811
+ const result = PathUtils.ensurePathWithin(inputPath, baseDir);
812
+ const expected = path.resolve(baseDir, inputPath);
813
+
814
+ expect(result).toBe(expected);
815
+ expect(result.startsWith(path.resolve(baseDir))).toBe(true);
816
+ });
817
+
818
+ it("应该拒绝超出基础目录的路径", () => {
819
+ const baseDir = "/safe/base";
820
+ const inputPath = "../../../etc/passwd";
821
+
822
+ expect(() => {
823
+ PathUtils.ensurePathWithin(inputPath, baseDir);
824
+ }).toThrow("路径 ../../../etc/passwd 超出了允许的范围");
825
+ });
826
+
827
+ it("应该处理绝对路径", () => {
828
+ const baseDir = "/safe/base";
829
+ const inputPath = "/safe/base/subdir/file.txt";
830
+
831
+ const result = PathUtils.ensurePathWithin(inputPath, baseDir);
832
+
833
+ expect(result).toBe(path.resolve(inputPath));
834
+ });
835
+
836
+ it("应该拒绝指向基础目录外的绝对路径", () => {
837
+ const baseDir = "/safe/base";
838
+ const inputPath = "/dangerous/path/file.txt";
839
+
840
+ expect(() => {
841
+ PathUtils.ensurePathWithin(inputPath, baseDir);
842
+ }).toThrow("路径 /dangerous/path/file.txt 超出了允许的范围");
843
+ });
844
+ });
845
+
846
+ describe("createSafePath 创建安全的文件路径", () => {
847
+ it("应该创建安全的路径", () => {
848
+ const segments = ["dir", "subdir", "file.txt"];
849
+
850
+ const result = PathUtils.createSafePath(...segments);
851
+ const expected = path.normalize(path.join(...segments));
852
+
853
+ expect(result).toBe(expected);
854
+ });
855
+
856
+ it("应该拒绝包含危险字符的路径", () => {
857
+ // 测试会导致规范化后包含 ".." 的路径
858
+ const dangerousSegments = [
859
+ ["dir", "..", "file.txt"], // 规范化后: dir/../file.txt -> file.txt (不包含 "..")
860
+ ["~", "file.txt"], // 规范化后: ~/file.txt (包含 "~")
861
+ ["dir", "subdir", "../../../etc/passwd"], // 规范化后: ../etc/passwd (包含 "..")
862
+ ];
863
+
864
+ // 只有包含 "~" 或规范化后仍包含 ".." 的路径会抛出错误
865
+ expect(() => {
866
+ PathUtils.createSafePath("~", "file.txt");
867
+ }).toThrow();
868
+
869
+ expect(() => {
870
+ PathUtils.createSafePath("dir", "subdir", "../../../etc/passwd");
871
+ }).toThrow();
872
+
873
+ // 这个不会抛出错误,因为规范化后是 "file.txt"
874
+ expect(() => {
875
+ PathUtils.createSafePath("dir", "..", "file.txt");
876
+ }).not.toThrow();
877
+ });
878
+
879
+ it("应该处理空的路径段", () => {
880
+ const result = PathUtils.createSafePath("dir", "", "file.txt");
881
+ const expected = path.normalize(path.join("dir", "", "file.txt"));
882
+
883
+ expect(result).toBe(expected);
884
+ });
885
+
886
+ it("应该处理单个路径段", () => {
887
+ const result = PathUtils.createSafePath("file.txt");
888
+
889
+ expect(result).toBe("file.txt");
890
+ });
891
+ });
892
+
893
+ describe("getTempDir 获取临时目录路径", () => {
894
+ it("应该返回系统临时目录", () => {
895
+ process.env.TMPDIR = undefined;
896
+ process.env.TEMP = undefined;
897
+ mockTmpdir.mockReturnValue("/system/tmp");
898
+
899
+ const result = PathUtils.getTempDir();
900
+
901
+ expect(result).toBe("/system/tmp");
902
+ expect(mockTmpdir).toHaveBeenCalled();
903
+ });
904
+
905
+ it("应该优先使用 TMPDIR 环境变量", () => {
906
+ process.env.TMPDIR = "/custom/tmp";
907
+
908
+ const result = PathUtils.getTempDir();
909
+
910
+ expect(result).toBe("/custom/tmp");
911
+ });
912
+
913
+ it("应该使用 TEMP 环境变量作为备选", () => {
914
+ process.env.TMPDIR = undefined;
915
+ process.env.TEMP = "/windows/temp";
916
+
917
+ const result = PathUtils.getTempDir();
918
+
919
+ expect(result).toBe("/windows/temp");
920
+ });
921
+
922
+ it("应该在没有环境变量时使用系统默认", () => {
923
+ process.env.TMPDIR = undefined;
924
+ process.env.TEMP = undefined;
925
+ mockTmpdir.mockReturnValue("/default/tmp");
926
+
927
+ const result = PathUtils.getTempDir();
928
+
929
+ expect(result).toBe("/default/tmp");
930
+ });
931
+ });
932
+
933
+ describe("getHomeDir 获取用户主目录路径", () => {
934
+ it("应该返回 HOME 环境变量", () => {
935
+ process.env.HOME = "/home/user";
936
+ process.env.USERPROFILE = undefined;
937
+
938
+ const result = PathUtils.getHomeDir();
939
+
940
+ expect(result).toBe("/home/user");
941
+ });
942
+
943
+ it("应该使用 USERPROFILE 作为备选", () => {
944
+ process.env.HOME = undefined;
945
+ process.env.USERPROFILE = "C:\\Users\\user";
946
+
947
+ const result = PathUtils.getHomeDir();
948
+
949
+ expect(result).toBe("C:\\Users\\user");
950
+ });
951
+
952
+ it("应该在没有环境变量时返回空字符串", () => {
953
+ process.env.HOME = undefined;
954
+ process.env.USERPROFILE = undefined;
955
+
956
+ const result = PathUtils.getHomeDir();
957
+
958
+ expect(result).toBe("");
959
+ });
960
+
961
+ it("应该优先使用 HOME 而不是 USERPROFILE", () => {
962
+ process.env.HOME = "/home/user";
963
+ process.env.USERPROFILE = "C:\\Users\\user";
964
+
965
+ const result = PathUtils.getHomeDir();
966
+
967
+ expect(result).toBe("/home/user");
968
+ });
969
+ });
970
+
971
+ describe("边界条件和特殊情况", () => {
972
+ it("应该处理空字符串路径", () => {
973
+ expect(() => PathUtils.createSafePath("")).not.toThrow();
974
+ expect(PathUtils.validatePath("")).toBe(true);
975
+ });
976
+
977
+ it("应该处理非常长的路径", () => {
978
+ const longPath = "a".repeat(1000);
979
+
980
+ expect(PathUtils.validatePath(longPath)).toBe(true);
981
+ expect(() => PathUtils.createSafePath(longPath)).not.toThrow();
982
+ });
983
+
984
+ it("应该处理包含特殊字符的路径", () => {
985
+ const specialChars = [
986
+ "file name with spaces.txt",
987
+ "file-with-dashes.txt",
988
+ "file_with_underscores.txt",
989
+ ];
990
+
991
+ for (const fileName of specialChars) {
992
+ expect(PathUtils.validatePath(fileName)).toBe(true);
993
+ expect(() => PathUtils.createSafePath("dir", fileName)).not.toThrow();
994
+ }
995
+ });
996
+
997
+ it("应该处理 Unicode 字符", () => {
998
+ const unicodePaths = ["文件.txt", "file.txt", "ファイル.txt"];
999
+
1000
+ for (const unicodePath of unicodePaths) {
1001
+ expect(PathUtils.validatePath(unicodePath)).toBe(true);
1002
+ expect(() =>
1003
+ PathUtils.createSafePath("dir", unicodePath)
1004
+ ).not.toThrow();
1005
+ }
1006
+ });
1007
+
1008
+ it("应该处理路径分隔符的不同组合", () => {
1009
+ const paths = [
1010
+ "dir/file.txt",
1011
+ "dir\\file.txt",
1012
+ "./dir/file.txt",
1013
+ ".\\dir\\file.txt",
1014
+ ];
1015
+
1016
+ for (const testPath of paths) {
1017
+ if (!testPath.includes("..")) {
1018
+ expect(PathUtils.validatePath(testPath)).toBe(true);
1019
+ }
1020
+ }
1021
+ });
1022
+ });
1023
+
1024
+ describe("跨平台兼容性", () => {
1025
+ it("应该处理 Windows 风格的路径", () => {
1026
+ const windowsPaths = [
1027
+ "C:\\Users\\user\\file.txt",
1028
+ "D:\\Projects\\app\\config.json",
1029
+ "\\\\server\\share\\file.txt",
1030
+ ];
1031
+
1032
+ for (const windowsPath of windowsPaths) {
1033
+ expect(() =>
1034
+ PathUtils.ensurePathWithin("file.txt", windowsPath)
1035
+ ).not.toThrow();
1036
+ }
1037
+ });
1038
+
1039
+ it("应该处理 Unix 风格的路径", () => {
1040
+ const unixPaths = [
1041
+ "/home/user/file.txt",
1042
+ "/var/log/app.log",
1043
+ "/tmp/temp-file.txt",
1044
+ ];
1045
+
1046
+ for (const unixPath of unixPaths) {
1047
+ expect(() =>
1048
+ PathUtils.ensurePathWithin("file.txt", unixPath)
1049
+ ).not.toThrow();
1050
+ }
1051
+ });
1052
+
1053
+ it("应该正确处理路径规范化", () => {
1054
+ const pathsToNormalize = [
1055
+ { path: "dir/./file.txt", shouldPass: true }, // 规范化后: dir/file.txt
1056
+ { path: "dir//file.txt", shouldPass: true }, // 规范化后: dir/file.txt
1057
+ { path: "dir/subdir/../file.txt", shouldPass: true }, // 规范化后: dir/file.txt (不包含 "..")
1058
+ { path: "../../../etc/passwd", shouldPass: false }, // 规范化后: ../../../etc/passwd (包含 "..")
1059
+ ];
1060
+
1061
+ for (const { path: pathToNormalize, shouldPass } of pathsToNormalize) {
1062
+ const result = PathUtils.validatePath(pathToNormalize);
1063
+ expect(result).toBe(shouldPass);
1064
+ }
1065
+ });
1066
+ });
1067
+
1068
+ describe("错误处理", () => {
1069
+ it("应该在 fileURLToPath 失败时处理错误", () => {
1070
+ mockFileURLToPath.mockImplementation(() => {
1071
+ throw new Error("Invalid URL");
1072
+ });
1073
+
1074
+ expect(() => PathUtils.getScriptDir()).toThrow("Invalid URL");
1075
+ });
1076
+
1077
+ it("应该处理无效的环境变量值", () => {
1078
+ process.env.XIAOZHI_CONFIG_DIR = null as any;
1079
+
1080
+ const result = PathUtils.getConfigDir();
1081
+
1082
+ expect(result).toBe(process.cwd());
1083
+ });
1084
+
1085
+ it("应该处理 tmpdir 函数失败", () => {
1086
+ mockTmpdir.mockImplementation(() => {
1087
+ throw new Error("Cannot access temp directory");
1088
+ });
1089
+ process.env.TMPDIR = undefined;
1090
+ process.env.TEMP = undefined;
1091
+
1092
+ expect(() => PathUtils.getTempDir()).toThrow(
1093
+ "Cannot access temp directory"
1094
+ );
1095
+ });
1096
+ });
1097
+
1098
+ describe("性能和内存测试", () => {
1099
+ it("应该高效处理大量路径操作", () => {
1100
+ const startTime = Date.now();
1101
+
1102
+ for (let i = 0; i < 1000; i++) {
1103
+ PathUtils.validatePath(`path/to/file${i}.txt`);
1104
+ PathUtils.createSafePath("dir", `file${i}.txt`);
1105
+ }
1106
+
1107
+ const endTime = Date.now();
1108
+ expect(endTime - startTime).toBeLessThan(100); // 应该在 100ms 内完成
1109
+ });
1110
+
1111
+ it("应该正确处理重复的路径操作", () => {
1112
+ const samePath = "test/path/file.txt";
1113
+
1114
+ for (let i = 0; i < 100; i++) {
1115
+ const result1 = PathUtils.validatePath(samePath);
1116
+ const result2 = PathUtils.createSafePath("test", "path", "file.txt");
1117
+
1118
+ expect(result1).toBe(true);
1119
+ expect(result2).toContain("file.txt");
1120
+ }
1121
+ });
1122
+ });
1123
+
1124
+ describe("集成测试", () => {
1125
+ it("应该在完整的工作流程中正确工作", () => {
1126
+ // 设置环境
1127
+ process.env.XIAOZHI_CONFIG_DIR = "/test/config";
1128
+ mockFileExists.mockReturnValue(true);
1129
+ mockFileURLToPath.mockReturnValue("/test/src/cli/utils/PathUtils.js");
1130
+
1131
+ // 测试完整的路径解析流程
1132
+ const configDir = PathUtils.getConfigDir();
1133
+ const workDir = PathUtils.getWorkDir();
1134
+ const configPath = PathUtils.resolveConfigPath("json");
1135
+ const pidFile = PathUtils.getPidFile();
1136
+
1137
+ expect(configDir).toBe("/test/config");
1138
+ expect(workDir).toBe(path.join("/test/config", ".xiaozhi"));
1139
+ expect(configPath).toBe(path.join("/test/config", "xiaozhi.config.json"));
1140
+ expect(pidFile).toBe(
1141
+ path.join("/test/config", ".xiaozhi-mcp-service.pid")
1142
+ );
1143
+ });
1144
+
1145
+ it("应该在没有配置的情况下使用合理的默认值", () => {
1146
+ // 清除所有环境变量
1147
+ process.env.XIAOZHI_CONFIG_DIR = undefined;
1148
+ process.env.TMPDIR = undefined;
1149
+ process.env.TEMP = undefined;
1150
+ process.env.HOME = undefined;
1151
+ process.env.USERPROFILE = undefined;
1152
+
1153
+ mockFileExists.mockReturnValue(false);
1154
+ mockTmpdir.mockReturnValue("/system/tmp");
1155
+
1156
+ const configDir = PathUtils.getConfigDir();
1157
+ const tempDir = PathUtils.getTempDir();
1158
+ const homeDir = PathUtils.getHomeDir();
1159
+
1160
+ expect(configDir).toBe(process.cwd());
1161
+ expect(tempDir).toBe("/system/tmp");
1162
+ expect(homeDir).toBe("");
1163
+ });
1164
+ });
1165
+ });