ssh-keyman 1.0.2 → 2.0.0
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/.github/BADGE-SUMMARY.md +244 -0
- package/.github/BADGES.md +214 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +36 -0
- package/.github/workflows/ci.yml +69 -0
- package/.github/workflows/publish.yml +32 -0
- package/.github/workflows/test-report.yml +50 -0
- package/CONTRIBUTING.md +128 -0
- package/STATUS.md +134 -0
- package/codecov.yml +29 -0
- package/package.json +34 -3
- package/readme.md +160 -25
- package/src/__tests__/cliOptions.test.js +78 -0
- package/src/__tests__/commands.test.js +369 -0
- package/src/__tests__/constants.test.js +44 -0
- package/src/__tests__/extendFs.test.js +105 -0
- package/src/__tests__/helpers.js +113 -0
- package/src/__tests__/testUtils.js +113 -0
- package/src/commands.js +170 -96
- package/src/constants.js +7 -36
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
const fs = require("fs-extra");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const os = require("os");
|
|
4
|
+
const { mockConsole, mockInquirer } = require("./helpers");
|
|
5
|
+
|
|
6
|
+
// Mock modules before importing commands
|
|
7
|
+
jest.mock("inquirer");
|
|
8
|
+
jest.mock("inquirer-autocomplete-prompt", () => jest.fn());
|
|
9
|
+
|
|
10
|
+
describe("commands", () => {
|
|
11
|
+
let testHomeDir;
|
|
12
|
+
let originalHomedir;
|
|
13
|
+
let consoleMock;
|
|
14
|
+
let SSH_PATH;
|
|
15
|
+
let KEYMAN_DIR_PATH;
|
|
16
|
+
let KEYMAN_PATH;
|
|
17
|
+
|
|
18
|
+
beforeAll(() => {
|
|
19
|
+
// Mock os.homedir to return test directory
|
|
20
|
+
originalHomedir = os.homedir;
|
|
21
|
+
testHomeDir = path.join(os.tmpdir(), `ssh-keyman-test-home-${Date.now()}`);
|
|
22
|
+
os.homedir = jest.fn(() => testHomeDir);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
afterAll(() => {
|
|
26
|
+
os.homedir = originalHomedir;
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
// Clean up test directory
|
|
31
|
+
if (fs.existsSync(testHomeDir)) {
|
|
32
|
+
fs.rmSync(testHomeDir, { recursive: true, force: true });
|
|
33
|
+
}
|
|
34
|
+
fs.mkdirSync(testHomeDir, { recursive: true });
|
|
35
|
+
|
|
36
|
+
SSH_PATH = path.join(testHomeDir, ".ssh");
|
|
37
|
+
KEYMAN_DIR_PATH = path.join(testHomeDir, ".sshkeyman");
|
|
38
|
+
KEYMAN_PATH = path.join(KEYMAN_DIR_PATH, ".sshkeyman");
|
|
39
|
+
|
|
40
|
+
// Create mock SSH directory
|
|
41
|
+
fs.mkdirSync(SSH_PATH, { recursive: true });
|
|
42
|
+
fs.writeFileSync(path.join(SSH_PATH, "id_rsa"), "mock private key");
|
|
43
|
+
fs.writeFileSync(path.join(SSH_PATH, "id_rsa.pub"), "mock public key");
|
|
44
|
+
|
|
45
|
+
consoleMock = mockConsole();
|
|
46
|
+
|
|
47
|
+
// Clear module cache to get fresh imports with mocked homedir
|
|
48
|
+
jest.resetModules();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
afterEach(() => {
|
|
52
|
+
consoleMock.restore();
|
|
53
|
+
if (fs.existsSync(testHomeDir)) {
|
|
54
|
+
fs.rmSync(testHomeDir, { recursive: true, force: true });
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe("init", () => {
|
|
59
|
+
it("should initialize ssh-keyman directory structure", () => {
|
|
60
|
+
const commands = require("../commands");
|
|
61
|
+
|
|
62
|
+
commands.init();
|
|
63
|
+
|
|
64
|
+
expect(fs.existsSync(KEYMAN_DIR_PATH)).toBe(true);
|
|
65
|
+
expect(fs.existsSync(KEYMAN_PATH)).toBe(true);
|
|
66
|
+
expect(fs.existsSync(path.join(KEYMAN_DIR_PATH, "default"))).toBe(true);
|
|
67
|
+
|
|
68
|
+
const keymanContent = JSON.parse(fs.readFileSync(KEYMAN_PATH, "utf8"));
|
|
69
|
+
expect(keymanContent).toEqual({
|
|
70
|
+
active: "default",
|
|
71
|
+
available: ["default"],
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("should not reinitialize if already initialized", () => {
|
|
76
|
+
const commands = require("../commands");
|
|
77
|
+
|
|
78
|
+
commands.init();
|
|
79
|
+
const logs1 = [...consoleMock.getLogs()];
|
|
80
|
+
consoleMock.clear();
|
|
81
|
+
|
|
82
|
+
commands.init();
|
|
83
|
+
const logs2 = consoleMock.getLogs();
|
|
84
|
+
|
|
85
|
+
expect(logs2.some(log => log.includes("already initialized"))).toBe(true);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe("help", () => {
|
|
90
|
+
it("should display help information", () => {
|
|
91
|
+
const commands = require("../commands");
|
|
92
|
+
|
|
93
|
+
commands.help();
|
|
94
|
+
|
|
95
|
+
const logs = consoleMock.getLogs().join("\n");
|
|
96
|
+
expect(logs).toContain("SSH KeyMan");
|
|
97
|
+
expect(logs).toContain("Usage:");
|
|
98
|
+
expect(logs).toContain("Commands:");
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe("version", () => {
|
|
103
|
+
it("should display version information", () => {
|
|
104
|
+
const commands = require("../commands");
|
|
105
|
+
|
|
106
|
+
commands.version();
|
|
107
|
+
|
|
108
|
+
const logs = consoleMock.getLogs().join("\n");
|
|
109
|
+
expect(logs).toContain("ssh-keyman");
|
|
110
|
+
expect(logs).toContain("version");
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe("list", () => {
|
|
115
|
+
it("should show error when not initialized", () => {
|
|
116
|
+
const commands = require("../commands");
|
|
117
|
+
|
|
118
|
+
commands.list();
|
|
119
|
+
|
|
120
|
+
const logs = consoleMock.getLogs().join("\n");
|
|
121
|
+
expect(logs).toContain("not initialized");
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("should list available environments when initialized", () => {
|
|
125
|
+
const commands = require("../commands");
|
|
126
|
+
|
|
127
|
+
commands.init();
|
|
128
|
+
|
|
129
|
+
// Reload module to pick up new keyman content
|
|
130
|
+
jest.resetModules();
|
|
131
|
+
const commandsReloaded = require("../commands");
|
|
132
|
+
consoleMock.clear();
|
|
133
|
+
|
|
134
|
+
commandsReloaded.list();
|
|
135
|
+
|
|
136
|
+
const logs = consoleMock.getLogs().join("\n");
|
|
137
|
+
expect(logs).toContain("Available environments");
|
|
138
|
+
expect(logs).toContain("default");
|
|
139
|
+
expect(logs).toContain("active");
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe("create", () => {
|
|
144
|
+
it("should show error when not initialized", async () => {
|
|
145
|
+
const commands = require("../commands");
|
|
146
|
+
|
|
147
|
+
await commands.create("test-env");
|
|
148
|
+
|
|
149
|
+
const logs = consoleMock.getLogs().join("\n");
|
|
150
|
+
expect(logs).toContain("not initialized");
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("should create new environment with name provided", async () => {
|
|
154
|
+
let inquirer = require("inquirer");
|
|
155
|
+
inquirer.prompt = jest.fn(() =>
|
|
156
|
+
Promise.resolve({ switchNow: false })
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
const commands = require("../commands");
|
|
160
|
+
commands.init();
|
|
161
|
+
|
|
162
|
+
// Reload to pick up initialized state
|
|
163
|
+
jest.resetModules();
|
|
164
|
+
inquirer = require("inquirer");
|
|
165
|
+
inquirer.prompt = jest.fn(() =>
|
|
166
|
+
Promise.resolve({ switchNow: false })
|
|
167
|
+
);
|
|
168
|
+
const commandsReloaded = require("../commands");
|
|
169
|
+
consoleMock.clear();
|
|
170
|
+
|
|
171
|
+
await commandsReloaded.create("production");
|
|
172
|
+
|
|
173
|
+
expect(fs.existsSync(path.join(KEYMAN_DIR_PATH, "production"))).toBe(true);
|
|
174
|
+
|
|
175
|
+
const keymanContent = JSON.parse(fs.readFileSync(KEYMAN_PATH, "utf8"));
|
|
176
|
+
expect(keymanContent.available).toContain("production");
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("should prompt for name when not provided", async () => {
|
|
180
|
+
let inquirer = require("inquirer");
|
|
181
|
+
let callCount = 0;
|
|
182
|
+
inquirer.prompt = jest.fn((questions) => {
|
|
183
|
+
callCount++;
|
|
184
|
+
const firstQuestion = questions[0];
|
|
185
|
+
if (firstQuestion.name === "envName") {
|
|
186
|
+
return Promise.resolve({ envName: "prompted-env" });
|
|
187
|
+
}
|
|
188
|
+
return Promise.resolve({ switchNow: false });
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const commands = require("../commands");
|
|
192
|
+
commands.init();
|
|
193
|
+
|
|
194
|
+
// Reload to pick up initialized state
|
|
195
|
+
jest.resetModules();
|
|
196
|
+
inquirer = require("inquirer");
|
|
197
|
+
callCount = 0;
|
|
198
|
+
inquirer.prompt = jest.fn((questions) => {
|
|
199
|
+
callCount++;
|
|
200
|
+
const firstQuestion = questions[0];
|
|
201
|
+
if (firstQuestion.name === "envName") {
|
|
202
|
+
return Promise.resolve({ envName: "prompted-env" });
|
|
203
|
+
}
|
|
204
|
+
return Promise.resolve({ switchNow: false });
|
|
205
|
+
});
|
|
206
|
+
const commandsReloaded = require("../commands");
|
|
207
|
+
consoleMock.clear();
|
|
208
|
+
|
|
209
|
+
await commandsReloaded.create();
|
|
210
|
+
|
|
211
|
+
expect(callCount).toBeGreaterThan(0);
|
|
212
|
+
expect(fs.existsSync(path.join(KEYMAN_DIR_PATH, "prompted-env"))).toBe(true);
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
describe("switch", () => {
|
|
217
|
+
it("should show error when not initialized", async () => {
|
|
218
|
+
const commands = require("../commands");
|
|
219
|
+
|
|
220
|
+
await commands.switch("production");
|
|
221
|
+
|
|
222
|
+
const logs = consoleMock.getLogs().join("\n");
|
|
223
|
+
expect(logs).toContain("not initialized");
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it("should switch to existing environment", async () => {
|
|
227
|
+
let inquirer = require("inquirer");
|
|
228
|
+
inquirer.prompt = jest.fn(() =>
|
|
229
|
+
Promise.resolve({ switchNow: false })
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
const commands = require("../commands");
|
|
233
|
+
commands.init();
|
|
234
|
+
|
|
235
|
+
// Reload to get initialized state
|
|
236
|
+
jest.resetModules();
|
|
237
|
+
inquirer = require("inquirer");
|
|
238
|
+
inquirer.prompt = jest.fn(() =>
|
|
239
|
+
Promise.resolve({ switchNow: false })
|
|
240
|
+
);
|
|
241
|
+
let commandsReloaded = require("../commands");
|
|
242
|
+
await commandsReloaded.create("production");
|
|
243
|
+
|
|
244
|
+
// Reload again to get updated environment list
|
|
245
|
+
jest.resetModules();
|
|
246
|
+
inquirer = require("inquirer");
|
|
247
|
+
inquirer.prompt = jest.fn(() =>
|
|
248
|
+
Promise.resolve({ switchNow: false })
|
|
249
|
+
);
|
|
250
|
+
commandsReloaded = require("../commands");
|
|
251
|
+
consoleMock.clear();
|
|
252
|
+
|
|
253
|
+
await commandsReloaded.switch("production");
|
|
254
|
+
|
|
255
|
+
const keymanContent = JSON.parse(fs.readFileSync(KEYMAN_PATH, "utf8"));
|
|
256
|
+
expect(keymanContent.active).toBe("production");
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it("should show warning when already on selected environment", async () => {
|
|
260
|
+
const commands = require("../commands");
|
|
261
|
+
commands.init();
|
|
262
|
+
|
|
263
|
+
// Reload to pick up initialized state
|
|
264
|
+
jest.resetModules();
|
|
265
|
+
const commandsReloaded = require("../commands");
|
|
266
|
+
consoleMock.clear();
|
|
267
|
+
|
|
268
|
+
await commandsReloaded.switch("default");
|
|
269
|
+
|
|
270
|
+
const logs = consoleMock.getLogs().join("\n");
|
|
271
|
+
expect(logs).toContain("already");
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
describe("delete", () => {
|
|
276
|
+
it("should show error when not initialized", async () => {
|
|
277
|
+
const commands = require("../commands");
|
|
278
|
+
|
|
279
|
+
await commands.delete("test-env");
|
|
280
|
+
|
|
281
|
+
const logs = consoleMock.getLogs().join("\n");
|
|
282
|
+
expect(logs).toContain("not initialized");
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it("should not delete default environment", async () => {
|
|
286
|
+
const commands = require("../commands");
|
|
287
|
+
commands.init();
|
|
288
|
+
|
|
289
|
+
// Reload to get initialized state
|
|
290
|
+
jest.resetModules();
|
|
291
|
+
const commandsReloaded = require("../commands");
|
|
292
|
+
consoleMock.clear();
|
|
293
|
+
|
|
294
|
+
await commandsReloaded.delete("default");
|
|
295
|
+
|
|
296
|
+
const logs = consoleMock.getLogs().join("\n");
|
|
297
|
+
expect(logs).toContain("Default environment cannot be deleted");
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it("should not delete active environment", async () => {
|
|
301
|
+
let inquirer = require("inquirer");
|
|
302
|
+
inquirer.prompt = jest.fn(() =>
|
|
303
|
+
Promise.resolve({ switchNow: true })
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
const commands = require("../commands");
|
|
307
|
+
commands.init();
|
|
308
|
+
|
|
309
|
+
// Reload and create new env
|
|
310
|
+
jest.resetModules();
|
|
311
|
+
inquirer = require("inquirer");
|
|
312
|
+
inquirer.prompt = jest.fn(() =>
|
|
313
|
+
Promise.resolve({ switchNow: true })
|
|
314
|
+
);
|
|
315
|
+
let commandsReloaded = require("../commands");
|
|
316
|
+
await commandsReloaded.create("production");
|
|
317
|
+
|
|
318
|
+
// Reload to get updated content
|
|
319
|
+
jest.resetModules();
|
|
320
|
+
inquirer = require("inquirer");
|
|
321
|
+
inquirer.prompt = jest.fn(() =>
|
|
322
|
+
Promise.resolve({ switchNow: true })
|
|
323
|
+
);
|
|
324
|
+
commandsReloaded = require("../commands");
|
|
325
|
+
consoleMock.clear();
|
|
326
|
+
|
|
327
|
+
await commandsReloaded.delete("production");
|
|
328
|
+
|
|
329
|
+
const logs = consoleMock.getLogs().join("\n");
|
|
330
|
+
expect(logs).toContain("currently active");
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it("should delete non-active environment", async () => {
|
|
334
|
+
let inquirer = require("inquirer");
|
|
335
|
+
inquirer.prompt = jest.fn(() =>
|
|
336
|
+
Promise.resolve({ switchNow: false })
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
const commands = require("../commands");
|
|
340
|
+
commands.init();
|
|
341
|
+
|
|
342
|
+
// Reload and create new env
|
|
343
|
+
jest.resetModules();
|
|
344
|
+
inquirer = require("inquirer");
|
|
345
|
+
inquirer.prompt = jest.fn(() =>
|
|
346
|
+
Promise.resolve({ switchNow: false })
|
|
347
|
+
);
|
|
348
|
+
let commandsReloaded = require("../commands");
|
|
349
|
+
await commandsReloaded.create("production");
|
|
350
|
+
|
|
351
|
+
// Reload to get updated content
|
|
352
|
+
jest.resetModules();
|
|
353
|
+
inquirer = require("inquirer");
|
|
354
|
+
inquirer.prompt = jest.fn(() =>
|
|
355
|
+
Promise.resolve({ switchNow: false })
|
|
356
|
+
);
|
|
357
|
+
commandsReloaded = require("../commands");
|
|
358
|
+
consoleMock.clear();
|
|
359
|
+
|
|
360
|
+
await commandsReloaded.delete("production");
|
|
361
|
+
|
|
362
|
+
expect(fs.existsSync(path.join(KEYMAN_DIR_PATH, "production"))).toBe(false);
|
|
363
|
+
|
|
364
|
+
const keymanContent = JSON.parse(fs.readFileSync(KEYMAN_PATH, "utf8"));
|
|
365
|
+
expect(keymanContent.available).not.toContain("production");
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const { cliOptions, options } = require("../constants");
|
|
2
|
+
|
|
3
|
+
describe("constants", () => {
|
|
4
|
+
describe("cliOptions", () => {
|
|
5
|
+
it("should be an array of CLI option definitions", () => {
|
|
6
|
+
expect(Array.isArray(cliOptions)).toBe(true);
|
|
7
|
+
expect(cliOptions.length).toBeGreaterThan(0);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("should have correct structure for each option", () => {
|
|
11
|
+
cliOptions.forEach((option) => {
|
|
12
|
+
expect(Array.isArray(option)).toBe(true);
|
|
13
|
+
expect(option.length).toBe(3);
|
|
14
|
+
expect(typeof option[0]).toBe("string"); // short option
|
|
15
|
+
expect(typeof option[1]).toBe("string"); // long option name
|
|
16
|
+
expect(typeof option[2]).toBe("string"); // help text
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("should include all required commands", () => {
|
|
21
|
+
const commands = cliOptions.map((opt) => opt[0]);
|
|
22
|
+
expect(commands).toContain("i"); // init
|
|
23
|
+
expect(commands).toContain("c"); // create
|
|
24
|
+
expect(commands).toContain("s"); // switch
|
|
25
|
+
expect(commands).toContain("d"); // delete
|
|
26
|
+
expect(commands).toContain("ls"); // list
|
|
27
|
+
expect(commands).toContain("h"); // help
|
|
28
|
+
expect(commands).toContain("v"); // version
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("should have unique option names", () => {
|
|
32
|
+
const shortNames = cliOptions.map((opt) => opt[0]);
|
|
33
|
+
const uniqueNames = new Set(shortNames);
|
|
34
|
+
expect(uniqueNames.size).toBe(shortNames.length);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe("options", () => {
|
|
39
|
+
it("should be an array", () => {
|
|
40
|
+
expect(Array.isArray(options)).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
const fs = require("fs-extra");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const os = require("os");
|
|
4
|
+
const { delDirSync, delAndCopySync } = require("../extendFs");
|
|
5
|
+
|
|
6
|
+
describe("extendFs", () => {
|
|
7
|
+
let testDir;
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
testDir = path.join(os.tmpdir(), `ssh-keyman-test-${Date.now()}`);
|
|
11
|
+
fs.mkdirSync(testDir, { recursive: true });
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
if (fs.existsSync(testDir)) {
|
|
16
|
+
fs.rmSync(testDir, { recursive: true, force: true });
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe("delDirSync", () => {
|
|
21
|
+
it("should delete an empty directory", () => {
|
|
22
|
+
const emptyDir = path.join(testDir, "empty");
|
|
23
|
+
fs.mkdirSync(emptyDir);
|
|
24
|
+
|
|
25
|
+
delDirSync(emptyDir);
|
|
26
|
+
|
|
27
|
+
expect(fs.existsSync(emptyDir)).toBe(false);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("should delete a directory with files", () => {
|
|
31
|
+
const dirWithFiles = path.join(testDir, "withFiles");
|
|
32
|
+
fs.mkdirSync(dirWithFiles);
|
|
33
|
+
fs.writeFileSync(path.join(dirWithFiles, "file1.txt"), "content1");
|
|
34
|
+
fs.writeFileSync(path.join(dirWithFiles, "file2.txt"), "content2");
|
|
35
|
+
|
|
36
|
+
delDirSync(dirWithFiles);
|
|
37
|
+
|
|
38
|
+
expect(fs.existsSync(dirWithFiles)).toBe(false);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("should delete a directory with nested directories", () => {
|
|
42
|
+
const dirWithNested = path.join(testDir, "withNested");
|
|
43
|
+
const nestedDir = path.join(dirWithNested, "nested");
|
|
44
|
+
fs.mkdirSync(nestedDir, { recursive: true });
|
|
45
|
+
fs.writeFileSync(path.join(nestedDir, "file.txt"), "content");
|
|
46
|
+
|
|
47
|
+
delDirSync(dirWithNested);
|
|
48
|
+
|
|
49
|
+
expect(fs.existsSync(dirWithNested)).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe("delAndCopySync", () => {
|
|
54
|
+
it("should delete target directory and copy from source", () => {
|
|
55
|
+
const sourceDir = path.join(testDir, "source");
|
|
56
|
+
const targetDir = path.join(testDir, "target");
|
|
57
|
+
|
|
58
|
+
// Create source directory with files
|
|
59
|
+
fs.mkdirSync(sourceDir);
|
|
60
|
+
fs.writeFileSync(path.join(sourceDir, "file1.txt"), "source content 1");
|
|
61
|
+
fs.writeFileSync(path.join(sourceDir, "file2.txt"), "source content 2");
|
|
62
|
+
|
|
63
|
+
// Create target directory with different files
|
|
64
|
+
fs.mkdirSync(targetDir);
|
|
65
|
+
fs.writeFileSync(path.join(targetDir, "oldfile.txt"), "old content");
|
|
66
|
+
|
|
67
|
+
delAndCopySync(sourceDir, targetDir);
|
|
68
|
+
|
|
69
|
+
// Check that target exists
|
|
70
|
+
expect(fs.existsSync(targetDir)).toBe(true);
|
|
71
|
+
|
|
72
|
+
// Check that source files were copied
|
|
73
|
+
expect(fs.existsSync(path.join(targetDir, "file1.txt"))).toBe(true);
|
|
74
|
+
expect(fs.existsSync(path.join(targetDir, "file2.txt"))).toBe(true);
|
|
75
|
+
expect(fs.readFileSync(path.join(targetDir, "file1.txt"), "utf8")).toBe(
|
|
76
|
+
"source content 1"
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
// Check that old target file is gone
|
|
80
|
+
expect(fs.existsSync(path.join(targetDir, "oldfile.txt"))).toBe(false);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("should handle nested directories in copy", () => {
|
|
84
|
+
const sourceDir = path.join(testDir, "source");
|
|
85
|
+
const targetDir = path.join(testDir, "target");
|
|
86
|
+
const nestedSource = path.join(sourceDir, "nested");
|
|
87
|
+
|
|
88
|
+
// Create source with nested structure
|
|
89
|
+
fs.mkdirSync(nestedSource, { recursive: true });
|
|
90
|
+
fs.writeFileSync(path.join(nestedSource, "nested.txt"), "nested content");
|
|
91
|
+
|
|
92
|
+
// Create empty target
|
|
93
|
+
fs.mkdirSync(targetDir);
|
|
94
|
+
|
|
95
|
+
delAndCopySync(sourceDir, targetDir);
|
|
96
|
+
|
|
97
|
+
// Check nested structure was copied
|
|
98
|
+
expect(fs.existsSync(path.join(targetDir, "nested", "nested.txt"))).toBe(true);
|
|
99
|
+
expect(
|
|
100
|
+
fs.readFileSync(path.join(targetDir, "nested", "nested.txt"), "utf8")
|
|
101
|
+
).toBe("nested content");
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
const fs = require("fs-extra");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const os = require("os");
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Create a mock file system structure for testing
|
|
7
|
+
*/
|
|
8
|
+
function createMockFileSystem(baseDir) {
|
|
9
|
+
const sshPath = path.join(baseDir, ".ssh");
|
|
10
|
+
const keymanPath = path.join(baseDir, ".sshkeyman");
|
|
11
|
+
const keymanFile = path.join(keymanPath, ".sshkeyman");
|
|
12
|
+
const defaultEnvPath = path.join(keymanPath, "default");
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
sshPath,
|
|
16
|
+
keymanPath,
|
|
17
|
+
keymanFile,
|
|
18
|
+
defaultEnvPath,
|
|
19
|
+
setup: () => {
|
|
20
|
+
if (!fs.existsSync(baseDir)) {
|
|
21
|
+
fs.mkdirSync(baseDir, { recursive: true });
|
|
22
|
+
}
|
|
23
|
+
if (!fs.existsSync(sshPath)) {
|
|
24
|
+
fs.mkdirSync(sshPath, { recursive: true });
|
|
25
|
+
fs.writeFileSync(path.join(sshPath, "id_rsa"), "mock private key");
|
|
26
|
+
fs.writeFileSync(path.join(sshPath, "id_rsa.pub"), "mock public key");
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
cleanup: () => {
|
|
30
|
+
if (fs.existsSync(baseDir)) {
|
|
31
|
+
fs.rmSync(baseDir, { recursive: true, force: true });
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
initializeKeyman: () => {
|
|
35
|
+
if (!fs.existsSync(keymanPath)) {
|
|
36
|
+
fs.mkdirSync(keymanPath, { recursive: true });
|
|
37
|
+
}
|
|
38
|
+
if (!fs.existsSync(defaultEnvPath)) {
|
|
39
|
+
fs.mkdirSync(defaultEnvPath, { recursive: true });
|
|
40
|
+
fs.copySync(sshPath, defaultEnvPath);
|
|
41
|
+
}
|
|
42
|
+
fs.writeFileSync(
|
|
43
|
+
keymanFile,
|
|
44
|
+
JSON.stringify({ active: "default", available: ["default"] })
|
|
45
|
+
);
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Mock console methods
|
|
52
|
+
*/
|
|
53
|
+
function mockConsole() {
|
|
54
|
+
const originalLog = console.log;
|
|
55
|
+
const originalError = console.error;
|
|
56
|
+
const logs = [];
|
|
57
|
+
const errors = [];
|
|
58
|
+
|
|
59
|
+
console.log = (...args) => {
|
|
60
|
+
logs.push(args.join(" "));
|
|
61
|
+
};
|
|
62
|
+
console.error = (...args) => {
|
|
63
|
+
errors.push(args.join(" "));
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
logs,
|
|
68
|
+
errors,
|
|
69
|
+
restore: () => {
|
|
70
|
+
console.log = originalLog;
|
|
71
|
+
console.error = originalError;
|
|
72
|
+
},
|
|
73
|
+
getLogs: () => logs,
|
|
74
|
+
getErrors: () => errors,
|
|
75
|
+
clear: () => {
|
|
76
|
+
logs.length = 0;
|
|
77
|
+
errors.length = 0;
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Mock inquirer prompts
|
|
84
|
+
*/
|
|
85
|
+
function mockInquirer(answers = {}) {
|
|
86
|
+
const inquirer = require("inquirer");
|
|
87
|
+
const originalPrompt = inquirer.prompt;
|
|
88
|
+
|
|
89
|
+
inquirer.prompt = jest.fn((questions) => {
|
|
90
|
+
const responses = {};
|
|
91
|
+
questions.forEach((q) => {
|
|
92
|
+
if (answers[q.name] !== undefined) {
|
|
93
|
+
responses[q.name] = answers[q.name];
|
|
94
|
+
} else if (q.default !== undefined) {
|
|
95
|
+
responses[q.name] = q.default;
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
return Promise.resolve(responses);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
restore: () => {
|
|
103
|
+
inquirer.prompt = originalPrompt;
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = {
|
|
109
|
+
createMockFileSystem,
|
|
110
|
+
mockConsole,
|
|
111
|
+
mockInquirer,
|
|
112
|
+
};
|
|
113
|
+
|