puter-cli 1.8.6 → 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/workflows/npm-build.yml +4 -3
- package/CHANGELOG.md +29 -0
- package/README.md +10 -2
- package/bin/index.js +184 -30
- package/package.json +12 -12
- package/src/commands/apps.js +53 -139
- package/src/commands/auth.js +113 -115
- package/src/commands/deploy.js +29 -27
- package/src/commands/files.js +151 -512
- package/src/commands/shell.js +13 -25
- package/src/commands/sites.js +25 -83
- package/src/commands/subdomains.js +46 -55
- package/src/commons.js +2 -2
- package/src/executor.js +26 -26
- package/src/modules/ErrorModule.js +18 -31
- package/src/modules/ProfileModule.js +183 -123
- package/src/modules/PuterModule.js +30 -0
- package/tests/ErrorModule.test.js +42 -0
- package/tests/ProfileModule.test.js +274 -0
- package/tests/PuterModule.test.js +56 -0
- package/tests/apps.test.js +194 -0
- package/tests/commons.test.js +380 -0
- package/tests/deploy.test.js +84 -0
- package/tests/executor.test.js +52 -0
- package/tests/files.test.js +640 -0
- package/tests/login.test.js +69 -51
- package/tests/shell.test.js +184 -0
- package/tests/sites.test.js +67 -0
- package/tests/subdomains.test.js +90 -0
- package/src/modules/SetContextModule.js +0 -5
- package/src/temporary/context_helpers.js +0 -17
|
@@ -0,0 +1,640 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import { createFile, listFiles, pathExists, makeDirectory, renameFileOrDirectory, getInfo, showCwd, getDiskUsage } from "../src/commands/files.js";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import * as PuterModule from "../src/modules/PuterModule.js";
|
|
5
|
+
import * as auth from "../src/commands/auth.js";
|
|
6
|
+
import * as commons from "../src/commons.js";
|
|
7
|
+
import path from "path";
|
|
8
|
+
import * as utils from "../src/utils.js";
|
|
9
|
+
|
|
10
|
+
// Mock console to prevent actual logging
|
|
11
|
+
vi.spyOn(console, "log").mockImplementation(() => {});
|
|
12
|
+
vi.spyOn(console, "error").mockImplementation(() => {});
|
|
13
|
+
|
|
14
|
+
// Mock dependencies
|
|
15
|
+
vi.mock("chalk", () => ({
|
|
16
|
+
default: {
|
|
17
|
+
green: vi.fn((text) => text),
|
|
18
|
+
red: vi.fn((text) => text),
|
|
19
|
+
dim: vi.fn((text) => text),
|
|
20
|
+
yellow: vi.fn((text) => text),
|
|
21
|
+
cyan: vi.fn((text) => text),
|
|
22
|
+
white: vi.fn((text) => text),
|
|
23
|
+
},
|
|
24
|
+
}));
|
|
25
|
+
vi.mock("node-fetch");
|
|
26
|
+
const mockConfigStore = {};
|
|
27
|
+
vi.mock("conf", () => {
|
|
28
|
+
const Conf = vi.fn(() => ({
|
|
29
|
+
get: vi.fn((key) => mockConfigStore[key]),
|
|
30
|
+
set: vi.fn((key, value) => { mockConfigStore[key] = value; }),
|
|
31
|
+
clear: vi.fn(),
|
|
32
|
+
}));
|
|
33
|
+
return { default: Conf };
|
|
34
|
+
});
|
|
35
|
+
vi.mock("../src/modules/PuterModule.js");
|
|
36
|
+
vi.mock("../src/commands/auth.js");
|
|
37
|
+
vi.mock("../src/commons.js");
|
|
38
|
+
vi.mock("../src/utils.js");
|
|
39
|
+
|
|
40
|
+
const mockPuter = {
|
|
41
|
+
fs: {
|
|
42
|
+
stat: vi.fn(),
|
|
43
|
+
space: vi.fn(),
|
|
44
|
+
mkdir: vi.fn(),
|
|
45
|
+
upload: vi.fn(),
|
|
46
|
+
readdir: vi.fn(),
|
|
47
|
+
move: vi.fn(),
|
|
48
|
+
rename: vi.fn(),
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
describe("createFile", () => {
|
|
53
|
+
|
|
54
|
+
beforeEach(() => {
|
|
55
|
+
vi.clearAllMocks();
|
|
56
|
+
vi.spyOn(PuterModule, "getPuter").mockReturnValue(mockPuter);
|
|
57
|
+
vi.spyOn(auth, "getCurrentUserName").mockReturnValue("testuser");
|
|
58
|
+
vi.spyOn(auth, "getCurrentDirectory").mockReturnValue("/testuser/files");
|
|
59
|
+
vi.spyOn(commons, "resolvePath").mockImplementation((current, newPath) =>
|
|
60
|
+
path.join(current, newPath)
|
|
61
|
+
);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("should create a file successfully", async () => {
|
|
65
|
+
mockPuter.fs.stat.mockRejectedValue({ code: "subject_does_not_exist" });
|
|
66
|
+
mockPuter.fs.space.mockResolvedValue({ used: 500, capacity: 1000 });
|
|
67
|
+
mockPuter.fs.upload.mockResolvedValue({
|
|
68
|
+
name: "test.txt",
|
|
69
|
+
path: "/testuser/files/test.txt",
|
|
70
|
+
uid: "file-uid",
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const result = await createFile(["test.txt", "hello world"]);
|
|
74
|
+
|
|
75
|
+
expect(result).toBe(true);
|
|
76
|
+
expect(mockPuter.fs.upload).toHaveBeenCalled();
|
|
77
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
78
|
+
expect.stringContaining('File "test.txt" created successfully!')
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("should show usage and return false if no arguments are provided", async () => {
|
|
83
|
+
const result = await createFile([]);
|
|
84
|
+
expect(result).toBe(false);
|
|
85
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
86
|
+
chalk.red("Usage: touch <file_name> [content]")
|
|
87
|
+
);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("should overwrite an existing file", async () => {
|
|
91
|
+
mockPuter.fs.stat.mockResolvedValue({ id: "existing-file-id" });
|
|
92
|
+
mockPuter.fs.space.mockResolvedValue({ used: 500, capacity: 1000 });
|
|
93
|
+
mockPuter.fs.upload.mockResolvedValue({
|
|
94
|
+
name: "test.txt",
|
|
95
|
+
path: "/testuser/files/test.txt",
|
|
96
|
+
uid: "file-uid",
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const result = await createFile(["test.txt", "new content"]);
|
|
100
|
+
|
|
101
|
+
expect(result).toBe(true);
|
|
102
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
103
|
+
chalk.yellow('File "test.txt" already exists. It will be overwritten.')
|
|
104
|
+
);
|
|
105
|
+
expect(mockPuter.fs.upload).toHaveBeenCalled();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("should return false if there is not enough disk space", async () => {
|
|
109
|
+
mockPuter.fs.stat.mockRejectedValue({ code: "subject_does_not_exist" });
|
|
110
|
+
mockPuter.fs.space.mockResolvedValue({ used: 1000, capacity: 1000 });
|
|
111
|
+
vi.spyOn(commons, "showDiskSpaceUsage").mockImplementation(() => {});
|
|
112
|
+
|
|
113
|
+
const result = await createFile(["test.txt"]);
|
|
114
|
+
|
|
115
|
+
expect(result).toBe(false);
|
|
116
|
+
expect(console.error).toHaveBeenCalledWith(
|
|
117
|
+
chalk.red("Not enough disk space to create the file.")
|
|
118
|
+
);
|
|
119
|
+
expect(commons.showDiskSpaceUsage).toHaveBeenCalled();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("should create missing directories", async () => {
|
|
123
|
+
// First stat for file fails, second for directory also fails
|
|
124
|
+
mockPuter.fs.stat
|
|
125
|
+
.mockRejectedValueOnce({ code: "subject_does_not_exist" }) // file check
|
|
126
|
+
.mockRejectedValueOnce({ code: "subject_does_not_exist" }); // dir check
|
|
127
|
+
mockPuter.fs.space.mockResolvedValue({ used: 500, capacity: 1000 });
|
|
128
|
+
mockPuter.fs.mkdir.mockResolvedValue({});
|
|
129
|
+
mockPuter.fs.upload.mockResolvedValue({
|
|
130
|
+
name: "test.txt",
|
|
131
|
+
path: "/testuser/files/newdir/test.txt",
|
|
132
|
+
uid: "file-uid",
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const result = await createFile(["newdir/test.txt", "content"]);
|
|
136
|
+
|
|
137
|
+
expect(result).toBe(true);
|
|
138
|
+
expect(mockPuter.fs.mkdir).toHaveBeenCalledWith("/testuser/files/newdir", {
|
|
139
|
+
overwrite: false,
|
|
140
|
+
dedupeName: true,
|
|
141
|
+
createMissingParents: true,
|
|
142
|
+
});
|
|
143
|
+
expect(mockPuter.fs.upload).toHaveBeenCalled();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("should handle API errors during file creation", async () => {
|
|
147
|
+
mockPuter.fs.stat.mockRejectedValue({ code: "subject_does_not_exist" });
|
|
148
|
+
mockPuter.fs.space.mockResolvedValue({ used: 500, capacity: 1000 });
|
|
149
|
+
mockPuter.fs.upload.mockRejectedValue(new Error("API Error"));
|
|
150
|
+
|
|
151
|
+
const result = await createFile(["test.txt"]);
|
|
152
|
+
|
|
153
|
+
expect(result).toBe(false);
|
|
154
|
+
expect(console.error).toHaveBeenCalledWith(
|
|
155
|
+
chalk.red("Failed to create file.\nError: API Error")
|
|
156
|
+
);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe("listFiles", () => {
|
|
161
|
+
beforeEach(() => {
|
|
162
|
+
vi.clearAllMocks();
|
|
163
|
+
vi.spyOn(PuterModule, "getPuter").mockReturnValue(mockPuter);
|
|
164
|
+
vi.spyOn(auth, "getCurrentUserName").mockReturnValue("testuser");
|
|
165
|
+
vi.spyOn(auth, "getCurrentDirectory").mockReturnValue("/testuser/files");
|
|
166
|
+
vi.spyOn(commons, "resolvePath").mockImplementation((current, newPath) =>
|
|
167
|
+
path.join(current, newPath)
|
|
168
|
+
);
|
|
169
|
+
vi.spyOn(utils, "formatSize").mockImplementation((size) => `${size}B`);
|
|
170
|
+
vi.spyOn(utils, "formatDateTime").mockImplementation(() => "2024-01-01 12:00");
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("should list files successfully with mocked items", async () => {
|
|
174
|
+
const mockFiles = [
|
|
175
|
+
{ name: "file1.txt", is_dir: false, writable: true, size: 1024, modified: 1704067200, uid: "abc-123-def-456" },
|
|
176
|
+
{ name: "folder1", is_dir: true, writable: true, size: 0, modified: 1704067200, uid: "ghi-789-jkl-012" },
|
|
177
|
+
];
|
|
178
|
+
|
|
179
|
+
mockPuter.fs.stat.mockResolvedValue({ id: "dir-id" });
|
|
180
|
+
mockPuter.fs.readdir.mockResolvedValue(mockFiles);
|
|
181
|
+
|
|
182
|
+
await listFiles(["/testuser/files"]);
|
|
183
|
+
|
|
184
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
185
|
+
expect.stringContaining("Listing files in")
|
|
186
|
+
);
|
|
187
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
188
|
+
expect.stringContaining("file1.txt")
|
|
189
|
+
);
|
|
190
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
191
|
+
expect.stringContaining("folder1")
|
|
192
|
+
);
|
|
193
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
194
|
+
expect.stringContaining("There are 2 object(s).")
|
|
195
|
+
);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("should handle empty directory", async () => {
|
|
199
|
+
mockPuter.fs.stat.mockResolvedValue({ id: "dir-id" });
|
|
200
|
+
mockPuter.fs.readdir.mockResolvedValue([]);
|
|
201
|
+
|
|
202
|
+
await listFiles(["/testuser/files"]);
|
|
203
|
+
|
|
204
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
205
|
+
chalk.red("No files or directories found.")
|
|
206
|
+
);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it("should handle error when listing files fails", async () => {
|
|
210
|
+
mockPuter.fs.stat.mockResolvedValue({ id: "dir-id" });
|
|
211
|
+
mockPuter.fs.readdir.mockRejectedValue(new Error("Network error"));
|
|
212
|
+
|
|
213
|
+
await listFiles(["/testuser/files"]);
|
|
214
|
+
|
|
215
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
216
|
+
chalk.red("Failed to list files.")
|
|
217
|
+
);
|
|
218
|
+
expect(console.error).toHaveBeenCalledWith(
|
|
219
|
+
chalk.red("Error: Network error")
|
|
220
|
+
);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("should resolve relative path using getCurrentDirectory", async () => {
|
|
224
|
+
const mockFiles = [
|
|
225
|
+
{ name: "test.txt", is_dir: false, writable: true, size: 512, modified: 1704067200, uid: "xyz-999-abc-111" },
|
|
226
|
+
];
|
|
227
|
+
|
|
228
|
+
mockPuter.fs.stat.mockResolvedValue({ id: "dir-id" });
|
|
229
|
+
mockPuter.fs.readdir.mockResolvedValue(mockFiles);
|
|
230
|
+
|
|
231
|
+
await listFiles(["subdir"]);
|
|
232
|
+
|
|
233
|
+
expect(commons.resolvePath).toHaveBeenCalledWith("/testuser/files", "subdir");
|
|
234
|
+
expect(mockPuter.fs.readdir).toHaveBeenCalledWith("/testuser/files/subdir");
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("should use absolute path directly without resolving", async () => {
|
|
238
|
+
const mockFiles = [
|
|
239
|
+
{ name: "test.txt", is_dir: false, writable: true, size: 512, modified: 1704067200, uid: "xyz-999-abc-111" },
|
|
240
|
+
];
|
|
241
|
+
|
|
242
|
+
mockPuter.fs.stat.mockResolvedValue({ id: "dir-id" });
|
|
243
|
+
mockPuter.fs.readdir.mockResolvedValue(mockFiles);
|
|
244
|
+
|
|
245
|
+
await listFiles(["/absolute/path"]);
|
|
246
|
+
|
|
247
|
+
expect(commons.resolvePath).not.toHaveBeenCalled();
|
|
248
|
+
expect(mockPuter.fs.readdir).toHaveBeenCalledWith("/absolute/path");
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it("should show message when path does not exist", async () => {
|
|
252
|
+
mockPuter.fs.stat.mockRejectedValue({ code: "subject_does_not_exist" });
|
|
253
|
+
|
|
254
|
+
await listFiles(["/nonexistent/path"]);
|
|
255
|
+
|
|
256
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
257
|
+
expect.stringContaining("doesn't exists!")
|
|
258
|
+
);
|
|
259
|
+
expect(mockPuter.fs.readdir).not.toHaveBeenCalled();
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it("should list files when path exists", async () => {
|
|
263
|
+
const mockFiles = [
|
|
264
|
+
{ name: "exists.txt", is_dir: false, writable: false, size: 256, modified: 1704067200, uid: "aaa-bbb-ccc-ddd" },
|
|
265
|
+
];
|
|
266
|
+
|
|
267
|
+
mockPuter.fs.stat.mockResolvedValue({ id: "existing-dir" });
|
|
268
|
+
mockPuter.fs.readdir.mockResolvedValue(mockFiles);
|
|
269
|
+
|
|
270
|
+
await listFiles(["/existing/path"]);
|
|
271
|
+
|
|
272
|
+
expect(mockPuter.fs.stat).toHaveBeenCalledWith("/existing/path");
|
|
273
|
+
expect(mockPuter.fs.readdir).toHaveBeenCalledWith("/existing/path");
|
|
274
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
275
|
+
expect.stringContaining("exists.txt")
|
|
276
|
+
);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it("should use current directory when no arguments provided", async () => {
|
|
280
|
+
const mockFiles = [
|
|
281
|
+
{ name: "default.txt", is_dir: false, writable: true, size: 100, modified: 1704067200, uid: "def-ault-uid-000" },
|
|
282
|
+
];
|
|
283
|
+
|
|
284
|
+
mockPuter.fs.stat.mockResolvedValue({ id: "dir-id" });
|
|
285
|
+
mockPuter.fs.readdir.mockResolvedValue(mockFiles);
|
|
286
|
+
|
|
287
|
+
await listFiles([]);
|
|
288
|
+
|
|
289
|
+
expect(commons.resolvePath).toHaveBeenCalledWith("/testuser/files", ".");
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
describe("makeDirectory", () => {
|
|
294
|
+
beforeEach(() => {
|
|
295
|
+
vi.clearAllMocks();
|
|
296
|
+
vi.spyOn(PuterModule, "getPuter").mockReturnValue(mockPuter);
|
|
297
|
+
vi.spyOn(auth, "getCurrentDirectory").mockReturnValue("/testuser/files");
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it("should show usage when no arguments provided", async () => {
|
|
301
|
+
await makeDirectory([]);
|
|
302
|
+
|
|
303
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
304
|
+
chalk.red("Usage: mkdir <directory_name>")
|
|
305
|
+
);
|
|
306
|
+
expect(mockPuter.fs.mkdir).not.toHaveBeenCalled();
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it("should create directory successfully", async () => {
|
|
310
|
+
mockPuter.fs.mkdir.mockResolvedValue({
|
|
311
|
+
id: "new-dir-id",
|
|
312
|
+
path: "/testuser/files/newdir",
|
|
313
|
+
uid: "dir-uid-123",
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
await makeDirectory(["newdir"]);
|
|
317
|
+
|
|
318
|
+
expect(mockPuter.fs.mkdir).toHaveBeenCalledWith("/testuser/files/newdir", {
|
|
319
|
+
overwrite: false,
|
|
320
|
+
dedupeName: true,
|
|
321
|
+
createMissingParents: false,
|
|
322
|
+
});
|
|
323
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
324
|
+
expect.stringContaining('Directory "newdir" created successfully!')
|
|
325
|
+
);
|
|
326
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
327
|
+
expect.stringContaining("Path: /testuser/files/newdir")
|
|
328
|
+
);
|
|
329
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
330
|
+
expect.stringContaining("UID: dir-uid-123")
|
|
331
|
+
);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it("should handle case when mkdir returns invalid response", async () => {
|
|
335
|
+
mockPuter.fs.mkdir.mockResolvedValue({});
|
|
336
|
+
|
|
337
|
+
await makeDirectory(["newdir"]);
|
|
338
|
+
|
|
339
|
+
expect(mockPuter.fs.mkdir).toHaveBeenCalled();
|
|
340
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
341
|
+
chalk.red("Failed to create directory. Please check your input.")
|
|
342
|
+
);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it("should handle case when mkdir returns null", async () => {
|
|
346
|
+
mockPuter.fs.mkdir.mockResolvedValue(null);
|
|
347
|
+
|
|
348
|
+
await makeDirectory(["newdir"]);
|
|
349
|
+
|
|
350
|
+
expect(mockPuter.fs.mkdir).toHaveBeenCalled();
|
|
351
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
352
|
+
chalk.red("Failed to create directory. Please check your input.")
|
|
353
|
+
);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it("should handle error when mkdir throws", async () => {
|
|
357
|
+
mockPuter.fs.mkdir.mockRejectedValue(new Error("Permission denied"));
|
|
358
|
+
|
|
359
|
+
await makeDirectory(["newdir"]);
|
|
360
|
+
|
|
361
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
362
|
+
chalk.red("Failed to create directory.")
|
|
363
|
+
);
|
|
364
|
+
expect(console.error).toHaveBeenCalledWith(
|
|
365
|
+
chalk.red("Error: Permission denied")
|
|
366
|
+
);
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
describe("renameFileOrDirectory", () => {
|
|
371
|
+
beforeEach(() => {
|
|
372
|
+
vi.clearAllMocks();
|
|
373
|
+
vi.spyOn(PuterModule, "getPuter").mockReturnValue(mockPuter);
|
|
374
|
+
vi.spyOn(auth, "getCurrentDirectory").mockReturnValue("/testuser/files");
|
|
375
|
+
vi.spyOn(commons, "resolvePath").mockImplementation((current, newPath) =>
|
|
376
|
+
path.join(current, newPath)
|
|
377
|
+
);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it("should show usage when less than 2 arguments provided", async () => {
|
|
381
|
+
await renameFileOrDirectory([]);
|
|
382
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
383
|
+
chalk.red("Usage: mv <source> <destination>")
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
vi.clearAllMocks();
|
|
387
|
+
await renameFileOrDirectory(["onlyOne"]);
|
|
388
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
389
|
+
chalk.red("Usage: mv <source> <destination>")
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
expect(mockPuter.fs.stat).not.toHaveBeenCalled();
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
it("should move file to directory successfully", async () => {
|
|
396
|
+
// Source file exists
|
|
397
|
+
mockPuter.fs.stat
|
|
398
|
+
.mockResolvedValueOnce({ uid: "source-uid-123", name: "file.txt" }) // source stat
|
|
399
|
+
.mockResolvedValueOnce({ is_dir: true }); // dest stat - it's a directory
|
|
400
|
+
|
|
401
|
+
mockPuter.fs.move.mockResolvedValue({
|
|
402
|
+
moved: { path: "/testuser/files/destdir/file.txt" },
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
await renameFileOrDirectory(["file.txt", "destdir"]);
|
|
406
|
+
|
|
407
|
+
expect(mockPuter.fs.stat).toHaveBeenCalledWith("/testuser/files/file.txt");
|
|
408
|
+
expect(mockPuter.fs.stat).toHaveBeenCalledWith("/testuser/files/destdir");
|
|
409
|
+
expect(mockPuter.fs.move).toHaveBeenCalledWith(
|
|
410
|
+
"source-uid-123",
|
|
411
|
+
"/testuser/files/destdir",
|
|
412
|
+
{
|
|
413
|
+
overwrite: false,
|
|
414
|
+
newName: "file.txt",
|
|
415
|
+
createMissingParents: false,
|
|
416
|
+
}
|
|
417
|
+
);
|
|
418
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
419
|
+
expect.stringContaining("Successfully moved")
|
|
420
|
+
);
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
it("should rename file successfully when destination is not a directory", async () => {
|
|
424
|
+
// Source file exists
|
|
425
|
+
mockPuter.fs.stat
|
|
426
|
+
.mockResolvedValueOnce({ uid: "source-uid-456", name: "oldname.txt" }) // source stat
|
|
427
|
+
.mockRejectedValueOnce({ code: "subject_does_not_exist" }); // dest doesn't exist
|
|
428
|
+
|
|
429
|
+
mockPuter.fs.rename.mockResolvedValue({
|
|
430
|
+
path: "/testuser/files/newname.txt",
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
await renameFileOrDirectory(["oldname.txt", "newname.txt"]);
|
|
434
|
+
|
|
435
|
+
expect(mockPuter.fs.stat).toHaveBeenCalledWith("/testuser/files/oldname.txt");
|
|
436
|
+
expect(mockPuter.fs.rename).toHaveBeenCalledWith("source-uid-456", "newname.txt");
|
|
437
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
438
|
+
expect.stringContaining("Successfully renamed")
|
|
439
|
+
);
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
it("should handle absolute paths directly", async () => {
|
|
443
|
+
mockPuter.fs.stat
|
|
444
|
+
.mockResolvedValueOnce({ uid: "abs-uid", name: "source.txt" })
|
|
445
|
+
.mockRejectedValueOnce({ code: "subject_does_not_exist" });
|
|
446
|
+
|
|
447
|
+
mockPuter.fs.rename.mockResolvedValue({
|
|
448
|
+
path: "/absolute/dest.txt",
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
await renameFileOrDirectory(["/absolute/source.txt", "/absolute/dest.txt"]);
|
|
452
|
+
|
|
453
|
+
expect(commons.resolvePath).not.toHaveBeenCalled();
|
|
454
|
+
expect(mockPuter.fs.stat).toHaveBeenCalledWith("/absolute/source.txt");
|
|
455
|
+
expect(mockPuter.fs.rename).toHaveBeenCalledWith("abs-uid", "dest.txt");
|
|
456
|
+
});
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
describe("getInfo", () => {
|
|
460
|
+
beforeEach(() => {
|
|
461
|
+
vi.clearAllMocks();
|
|
462
|
+
vi.spyOn(PuterModule, "getPuter").mockReturnValue(mockPuter);
|
|
463
|
+
vi.spyOn(auth, "getCurrentDirectory").mockReturnValue("/testuser/files");
|
|
464
|
+
vi.spyOn(utils, "formatSize").mockImplementation((size) => `${size}B`);
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
it("should display file info successfully", async () => {
|
|
468
|
+
mockPuter.fs.stat.mockResolvedValue({
|
|
469
|
+
name: "test.txt",
|
|
470
|
+
path: "/testuser/files/test.txt",
|
|
471
|
+
is_dir: false,
|
|
472
|
+
size: 1024,
|
|
473
|
+
created: 1704067200,
|
|
474
|
+
modified: 1704153600,
|
|
475
|
+
writable: true,
|
|
476
|
+
owner: { username: "testuser" },
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
await getInfo(["test.txt"]);
|
|
480
|
+
|
|
481
|
+
expect(mockPuter.fs.stat).toHaveBeenCalledWith("/testuser/files/test.txt");
|
|
482
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
483
|
+
expect.stringContaining("Getting stat info for")
|
|
484
|
+
);
|
|
485
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
486
|
+
expect.stringContaining("Name: ")
|
|
487
|
+
);
|
|
488
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
489
|
+
expect.stringContaining("Path: ")
|
|
490
|
+
);
|
|
491
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
492
|
+
expect.stringContaining("Type: ")
|
|
493
|
+
);
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
it("should handle case when stat returns null", async () => {
|
|
497
|
+
mockPuter.fs.stat.mockResolvedValue(null);
|
|
498
|
+
|
|
499
|
+
await getInfo(["notfound.txt"]);
|
|
500
|
+
|
|
501
|
+
expect(mockPuter.fs.stat).toHaveBeenCalledWith("/testuser/files/notfound.txt");
|
|
502
|
+
expect(console.error).toHaveBeenCalledWith(
|
|
503
|
+
chalk.red("Unable to get stat info. Please check your credentials.")
|
|
504
|
+
);
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
it("should handle error when stat throws", async () => {
|
|
508
|
+
mockPuter.fs.stat.mockRejectedValue(new Error("File not found"));
|
|
509
|
+
|
|
510
|
+
await getInfo(["error.txt"]);
|
|
511
|
+
|
|
512
|
+
expect(mockPuter.fs.stat).toHaveBeenCalledWith("/testuser/files/error.txt");
|
|
513
|
+
expect(console.error).toHaveBeenCalledWith(
|
|
514
|
+
chalk.red("Failed to get stat info.\nError: File not found")
|
|
515
|
+
);
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
it("should use current directory with default argument", async () => {
|
|
519
|
+
mockPuter.fs.stat.mockResolvedValue({
|
|
520
|
+
name: "files",
|
|
521
|
+
path: "/testuser/files",
|
|
522
|
+
is_dir: true,
|
|
523
|
+
size: 0,
|
|
524
|
+
created: 1704067200,
|
|
525
|
+
modified: 1704153600,
|
|
526
|
+
writable: true,
|
|
527
|
+
owner: { username: "testuser" },
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
await getInfo([]);
|
|
531
|
+
|
|
532
|
+
expect(mockPuter.fs.stat).toHaveBeenCalledWith("/testuser/files/.");
|
|
533
|
+
});
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
describe("showCwd", () => {
|
|
537
|
+
beforeEach(() => {
|
|
538
|
+
vi.clearAllMocks();
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
it("should display current working directory from config", async () => {
|
|
542
|
+
mockConfigStore.cwd = "/testuser/documents";
|
|
543
|
+
|
|
544
|
+
await showCwd();
|
|
545
|
+
|
|
546
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
547
|
+
chalk.green("/testuser/documents")
|
|
548
|
+
);
|
|
549
|
+
});
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
describe("getDiskUsage", () => {
|
|
553
|
+
beforeEach(() => {
|
|
554
|
+
vi.clearAllMocks();
|
|
555
|
+
vi.spyOn(PuterModule, "getPuter").mockReturnValue(mockPuter);
|
|
556
|
+
vi.spyOn(commons, "showDiskSpaceUsage").mockImplementation(() => {});
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
it("should fetch and display disk usage successfully", async () => {
|
|
560
|
+
const mockSpaceData = { used: 500, capacity: 1000 };
|
|
561
|
+
mockPuter.fs.space.mockResolvedValue(mockSpaceData);
|
|
562
|
+
|
|
563
|
+
await getDiskUsage();
|
|
564
|
+
|
|
565
|
+
expect(mockPuter.fs.space).toHaveBeenCalled();
|
|
566
|
+
expect(commons.showDiskSpaceUsage).toHaveBeenCalledWith(mockSpaceData);
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
it("should handle unable to fetch disk usage", async () => {
|
|
570
|
+
mockPuter.fs.space.mockResolvedValue(null);
|
|
571
|
+
|
|
572
|
+
await getDiskUsage();
|
|
573
|
+
|
|
574
|
+
expect(mockPuter.fs.space).toHaveBeenCalled();
|
|
575
|
+
expect(commons.showDiskSpaceUsage).not.toHaveBeenCalled();
|
|
576
|
+
expect(console.error).toHaveBeenCalledWith(
|
|
577
|
+
chalk.red("Unable to fetch disk usage information.")
|
|
578
|
+
);
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
it("should handle error when fetching disk usage", async () => {
|
|
582
|
+
mockPuter.fs.space.mockRejectedValue(new Error("Network failure"));
|
|
583
|
+
|
|
584
|
+
await getDiskUsage();
|
|
585
|
+
|
|
586
|
+
expect(mockPuter.fs.space).toHaveBeenCalled();
|
|
587
|
+
expect(commons.showDiskSpaceUsage).not.toHaveBeenCalled();
|
|
588
|
+
expect(console.error).toHaveBeenCalledWith(
|
|
589
|
+
chalk.red("Failed to fetch disk usage information.\nError: Network failure")
|
|
590
|
+
);
|
|
591
|
+
});
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
describe("pathExists", () => {
|
|
595
|
+
beforeEach(() => {
|
|
596
|
+
vi.clearAllMocks();
|
|
597
|
+
vi.spyOn(PuterModule, "getPuter").mockReturnValue(mockPuter);
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
it("should return false when no path provided", async () => {
|
|
601
|
+
const result = await pathExists("");
|
|
602
|
+
|
|
603
|
+
expect(result).toBe(false);
|
|
604
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
605
|
+
chalk.red("No path provided.")
|
|
606
|
+
);
|
|
607
|
+
expect(mockPuter.fs.stat).not.toHaveBeenCalled();
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
it("should return true when path exists", async () => {
|
|
611
|
+
mockPuter.fs.stat.mockResolvedValue({ id: "file-id" });
|
|
612
|
+
|
|
613
|
+
const result = await pathExists("/testuser/files/existing.txt");
|
|
614
|
+
|
|
615
|
+
expect(result).toBe(true);
|
|
616
|
+
expect(mockPuter.fs.stat).toHaveBeenCalledWith("/testuser/files/existing.txt");
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
it("should return false when subject does not exist", async () => {
|
|
620
|
+
mockPuter.fs.stat.mockRejectedValue({ code: "subject_does_not_exist" });
|
|
621
|
+
|
|
622
|
+
const result = await pathExists("/testuser/files/nonexistent.txt");
|
|
623
|
+
|
|
624
|
+
expect(result).toBe(false);
|
|
625
|
+
expect(mockPuter.fs.stat).toHaveBeenCalledWith("/testuser/files/nonexistent.txt");
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
it("should return false and log error on other errors", async () => {
|
|
629
|
+
const otherError = new Error("Unknown error");
|
|
630
|
+
mockPuter.fs.stat.mockRejectedValue(otherError);
|
|
631
|
+
|
|
632
|
+
const result = await pathExists("/testuser/files/error.txt");
|
|
633
|
+
|
|
634
|
+
expect(result).toBe(false);
|
|
635
|
+
expect(console.error).toHaveBeenCalledWith(
|
|
636
|
+
chalk.red("Failed to check if file exists.")
|
|
637
|
+
);
|
|
638
|
+
expect(console.error).toHaveBeenCalledWith("ERROR", otherError);
|
|
639
|
+
});
|
|
640
|
+
});
|