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.
@@ -1,18 +1,16 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- import { login, logout, getUserInfo, isAuthenticated, getAuthToken, getCurrentUserName,
3
- getCurrentDirectory, getUsageInfo } from '../src/commands/auth.js';
4
- import inquirer from 'inquirer';
5
- import ora from 'ora';
2
+ import {
3
+ login, logout, getUserInfo, isAuthenticated, getAuthToken, getCurrentUserName,
4
+ getUsageInfo
5
+ } from '../src/commands/auth.js';
6
6
  import chalk from 'chalk';
7
- import fetch from 'node-fetch';
8
7
  import Conf from 'conf';
9
- import { BASE_URL, PROJECT_NAME, API_BASE } from '../src/commons.js';
10
- import { ProfileAPI } from '../src/modules/ProfileModule.js';
11
- import * as contextHelpers from '../src/temporary/context_helpers.js';
8
+ import { PROJECT_NAME } from '../src/commons.js';
9
+ import * as PuterModule from '../src/modules/PuterModule.js';
12
10
 
13
11
  // Mock console to prevent actual logging
14
- vi.spyOn(console, 'log').mockImplementation(() => {});
15
- vi.spyOn(console, 'error').mockImplementation(() => {});
12
+ vi.spyOn(console, 'log').mockImplementation(() => { });
13
+ vi.spyOn(console, 'error').mockImplementation(() => { });
16
14
 
17
15
  // Mock dependencies
18
16
  vi.mock('inquirer');
@@ -25,7 +23,19 @@ vi.mock('chalk', () => ({
25
23
  cyan: vi.fn(text => text),
26
24
  }
27
25
  }));
28
- vi.mock('node-fetch');
26
+ vi.mock('../src/modules/PuterModule.js');
27
+
28
+ // Mock ProfileModule
29
+ const mockProfileModule = {
30
+ switchProfileWizard: vi.fn(),
31
+ getAuthToken: vi.fn(),
32
+ getCurrentProfile: vi.fn(),
33
+ };
34
+
35
+ vi.mock('../src/modules/ProfileModule.js', () => ({
36
+ getProfileModule: vi.fn(() => mockProfileModule),
37
+ initProfileModule: vi.fn(),
38
+ }));
29
39
 
30
40
  // Create a mock spinner object
31
41
  const mockSpinner = {
@@ -51,19 +61,6 @@ vi.mock('conf', () => {
51
61
  };
52
62
  });
53
63
 
54
- const mockProfileModule = {
55
- switchProfileWizard: vi.fn(),
56
- getAuthToken: vi.fn(),
57
- getCurrentProfile: vi.fn(),
58
- };
59
-
60
- const mockContext = {
61
- [ProfileAPI]: mockProfileModule,
62
- };
63
-
64
- vi.spyOn(contextHelpers, 'get_context').mockReturnValue(mockContext);
65
-
66
-
67
64
  describe('auth.js', () => {
68
65
  beforeEach(() => {
69
66
  vi.clearAllMocks();
@@ -71,13 +68,13 @@ describe('auth.js', () => {
71
68
 
72
69
  describe('login', () => {
73
70
  it('should login successfully with valid credentials', async () => {
74
- await login({}, mockContext);
71
+ await login({});
75
72
  expect(mockProfileModule.switchProfileWizard).toHaveBeenCalled();
76
73
  });
77
74
 
78
75
  it('should fail login with invalid credentials', async () => {
79
76
  mockProfileModule.switchProfileWizard.mockRejectedValue(new Error('Invalid credentials'));
80
- await expect(login({}, mockContext)).rejects.toThrow('Invalid credentials');
77
+ await expect(login({})).rejects.toThrow('Invalid credentials');
81
78
  expect(mockProfileModule.switchProfileWizard).toHaveBeenCalled();
82
79
  });
83
80
 
@@ -108,31 +105,40 @@ describe('auth.js', () => {
108
105
  it.skip('should handle logout error', async () => {
109
106
  // This test needs to be updated to reflect the new login flow
110
107
  });
111
-
108
+
112
109
  });
113
-
110
+
114
111
 
115
112
  describe('getUserInfo', () => {
113
+ const mockPuter = {
114
+ auth: {
115
+ getUser: vi.fn(),
116
+ },
117
+ };
118
+
119
+ beforeEach(() => {
120
+ vi.spyOn(PuterModule, 'getPuter').mockReturnValue(mockPuter);
121
+ });
122
+
116
123
  it('should fetch user info successfully', async () => {
117
- mockProfileModule.getAuthToken.mockReturnValue('testtoken');
118
- fetch.mockResolvedValue({
119
- json: () => Promise.resolve({
120
- username: 'testuser',
121
- }),
122
- ok: true,
124
+ mockPuter.auth.getUser.mockResolvedValue({
125
+ username: 'testuser',
126
+ uuid: 'testuuid',
127
+ email: 'test@puter.com',
128
+ email_confirmed: true,
129
+ is_temp: false,
130
+ human_readable_age: '1 day',
131
+ feature_flags: {},
123
132
  });
124
133
 
125
134
  await getUserInfo();
126
135
 
127
- expect(fetch).toHaveBeenCalledWith(`${API_BASE}/whoami`, {
128
- method: 'GET',
129
- headers: expect.any(Object),
130
- });
136
+ expect(mockPuter.auth.getUser).toHaveBeenCalled();
137
+ expect(console.log).toHaveBeenCalledWith(expect.stringContaining('User Information:'));
131
138
  });
132
139
 
133
140
  it('should handle fetch user info error', async () => {
134
- mockProfileModule.getAuthToken.mockReturnValue('testtoken');
135
- fetch.mockRejectedValue(new Error('Network error'));
141
+ mockPuter.auth.getUser.mockRejectedValue(new Error('Network error'));
136
142
  await getUserInfo();
137
143
  expect(console.error).toHaveBeenCalledWith(expect.stringContaining('Failed to get user info.'));
138
144
  });
@@ -161,28 +167,40 @@ describe('auth.js', () => {
161
167
  });
162
168
 
163
169
  describe('getUsageInfo', () => {
170
+ const mockPuter = {
171
+ auth: {
172
+ getMonthlyUsage: vi.fn(),
173
+ },
174
+ };
175
+
176
+ beforeEach(() => {
177
+ vi.spyOn(PuterModule, 'getPuter').mockReturnValue(mockPuter);
178
+ });
179
+
164
180
  it('should fetch usage info successfully', async () => {
165
- mockProfileModule.getAuthToken.mockReturnValue('testtoken');
166
- fetch.mockResolvedValue({
167
- json: vi.fn().mockResolvedValue({}),
168
- ok: true,
181
+ mockPuter.auth.getMonthlyUsage.mockResolvedValue({
182
+ allowanceInfo: {
183
+ monthUsageAllowance: 1000,
184
+ remaining: 500,
185
+ },
186
+ usage: {
187
+ total: 500,
188
+ },
189
+ appTotals: {},
169
190
  });
170
191
 
171
192
  await getUsageInfo();
172
193
 
173
- expect(fetch).toHaveBeenCalledWith(`${API_BASE}/drivers/usage`, {
174
- method: 'GET',
175
- headers: expect.any(Object),
176
- });
194
+ expect(mockPuter.auth.getMonthlyUsage).toHaveBeenCalled();
195
+ expect(console.log).toHaveBeenCalledWith(expect.stringContaining('Allowance Information:'));
177
196
  });
178
197
 
179
198
  it('should handle fetch usage info error', async () => {
180
- mockProfileModule.getAuthToken.mockReturnValue('testtoken');
181
- fetch.mockRejectedValue(new Error('Network error'));
199
+ mockPuter.auth.getMonthlyUsage.mockRejectedValue(new Error('Network error'));
182
200
  await getUsageInfo();
183
201
  expect(console.error).toHaveBeenCalledWith(expect.stringContaining('Failed to fetch usage information.'));
184
202
  });
185
203
  });
186
-
204
+
187
205
 
188
206
  });
@@ -0,0 +1,184 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import { execCommand, getPrompt } from '../src/executor.js';
3
+ import { getProfileModule } from '../src/modules/ProfileModule.js';
4
+ import readline from 'node:readline';
5
+
6
+ vi.mock('../src/executor.js');
7
+ vi.mock('../src/modules/ProfileModule.js');
8
+ vi.mock('node:readline');
9
+ vi.mock('conf', () => {
10
+ const Conf = vi.fn(() => ({
11
+ get: vi.fn(),
12
+ set: vi.fn(),
13
+ }));
14
+ return { default: Conf };
15
+ });
16
+
17
+ vi.spyOn(console, 'log').mockImplementation(() => { });
18
+ vi.spyOn(console, 'error').mockImplementation(() => { });
19
+
20
+ let updatePrompt;
21
+ let startShell;
22
+
23
+ beforeEach(async () => {
24
+ vi.resetModules();
25
+ vi.clearAllMocks();
26
+
27
+ vi.mocked(getPrompt).mockReturnValue('puter@/> ');
28
+ vi.mocked(getProfileModule).mockReturnValue({
29
+ checkLogin: vi.fn(),
30
+ });
31
+
32
+ const mockOn = vi.fn().mockReturnThis();
33
+ vi.mocked(readline.createInterface).mockReturnValue({
34
+ setPrompt: vi.fn(),
35
+ prompt: vi.fn(),
36
+ on: mockOn,
37
+ });
38
+
39
+ const module = await import('../src/commands/shell.js');
40
+ updatePrompt = module.updatePrompt;
41
+ startShell = module.startShell;
42
+ });
43
+
44
+ describe('updatePrompt', () => {
45
+ it('should call rl.setPrompt with getPrompt result', async () => {
46
+ await startShell();
47
+ const rl = readline.createInterface.mock.results[0].value;
48
+ rl.setPrompt.mockClear();
49
+
50
+ updatePrompt('/test/path');
51
+
52
+ expect(rl.setPrompt).toHaveBeenCalledWith('puter@/> ');
53
+ });
54
+ });
55
+
56
+ describe('startShell', () => {
57
+ it('should call checkLogin', async () => {
58
+ await startShell();
59
+ expect(getProfileModule().checkLogin).toHaveBeenCalled();
60
+ });
61
+
62
+ describe('with command argument', () => {
63
+ let mockExit;
64
+
65
+ beforeEach(() => {
66
+ mockExit = vi.spyOn(process, 'exit').mockImplementation(() => {
67
+ throw new Error('process.exit called');
68
+ });
69
+ });
70
+
71
+ afterEach(() => {
72
+ mockExit.mockRestore();
73
+ });
74
+
75
+ it('should execute command and exit when command is provided', async () => {
76
+ await expect(startShell('help')).rejects.toThrow('process.exit called');
77
+ expect(execCommand).toHaveBeenCalledWith('help');
78
+ expect(mockExit).toHaveBeenCalledWith(0);
79
+ });
80
+
81
+ it('should not create readline interface when command is provided', async () => {
82
+ vi.mocked(readline.createInterface).mockClear();
83
+ await expect(startShell('help')).rejects.toThrow('process.exit called');
84
+ expect(readline.createInterface).not.toHaveBeenCalled();
85
+ });
86
+ });
87
+
88
+ describe('without command argument (interactive mode)', () => {
89
+ it('should create readline interface', async () => {
90
+ await startShell();
91
+ expect(readline.createInterface).toHaveBeenCalledWith({
92
+ input: process.stdin,
93
+ output: process.stdout,
94
+ prompt: null,
95
+ });
96
+ });
97
+
98
+ it('should display welcome message', async () => {
99
+ await startShell();
100
+ expect(console.log).toHaveBeenCalledWith(expect.stringContaining('Welcome to Puter-CLI'));
101
+ });
102
+
103
+ it('should set prompt and call prompt()', async () => {
104
+ await startShell();
105
+ const rl = readline.createInterface.mock.results[0].value;
106
+ expect(rl.setPrompt).toHaveBeenCalledWith('puter@/> ');
107
+ expect(rl.prompt).toHaveBeenCalled();
108
+ });
109
+
110
+ it('should register line and close event handlers', async () => {
111
+ await startShell();
112
+ const rl = readline.createInterface.mock.results[0].value;
113
+ expect(rl.on).toHaveBeenCalledWith('line', expect.any(Function));
114
+ expect(rl.on).toHaveBeenCalledWith('close', expect.any(Function));
115
+ });
116
+ });
117
+
118
+ describe('line event handler', () => {
119
+ let lineHandler;
120
+ let rl;
121
+
122
+ beforeEach(async () => {
123
+ await startShell();
124
+ rl = readline.createInterface.mock.results[0].value;
125
+ lineHandler = rl.on.mock.calls.find(call => call[0] === 'line')[1];
126
+ });
127
+
128
+ it('should execute trimmed command', async () => {
129
+ await lineHandler(' ls ');
130
+ expect(execCommand).toHaveBeenCalledWith('ls');
131
+ });
132
+
133
+ it('should not execute empty line', async () => {
134
+ vi.mocked(execCommand).mockClear();
135
+ await lineHandler(' ');
136
+ expect(execCommand).not.toHaveBeenCalled();
137
+ });
138
+
139
+ it('should call prompt after executing command', async () => {
140
+ rl.prompt.mockClear();
141
+ await lineHandler('ls');
142
+ expect(rl.prompt).toHaveBeenCalled();
143
+ });
144
+
145
+ it('should log error message when execCommand throws', async () => {
146
+ vi.mocked(execCommand).mockRejectedValueOnce(new Error('Command failed'));
147
+ await lineHandler('badcmd');
148
+ expect(console.error).toHaveBeenCalledWith(expect.stringContaining('Command failed'));
149
+ });
150
+
151
+ it('should still call prompt after error', async () => {
152
+ vi.mocked(execCommand).mockRejectedValueOnce(new Error('Command failed'));
153
+ rl.prompt.mockClear();
154
+ await lineHandler('badcmd');
155
+ expect(rl.prompt).toHaveBeenCalled();
156
+ });
157
+ });
158
+
159
+ describe('close event handler', () => {
160
+ let closeHandler;
161
+ let mockExit;
162
+
163
+ beforeEach(async () => {
164
+ mockExit = vi.spyOn(process, 'exit').mockImplementation(() => { });
165
+ await startShell();
166
+ const rl = readline.createInterface.mock.results[0].value;
167
+ closeHandler = rl.on.mock.calls.find(call => call[0] === 'close')[1];
168
+ });
169
+
170
+ afterEach(() => {
171
+ mockExit.mockRestore();
172
+ });
173
+
174
+ it('should display goodbye message', () => {
175
+ closeHandler();
176
+ expect(console.log).toHaveBeenCalledWith(expect.stringContaining('Goodbye'));
177
+ });
178
+
179
+ it('should exit with code 0', () => {
180
+ closeHandler();
181
+ expect(mockExit).toHaveBeenCalledWith(0);
182
+ });
183
+ });
184
+ });
@@ -0,0 +1,67 @@
1
+ import { describe, vi, expect, it } from "vitest";
2
+ import { createSite, deleteSite, infoSite, listSites } from "../src/commands/sites";
3
+ import { createSubdomain, deleteSubdomain, getSubdomains } from "../src/commands/subdomains";
4
+ import { getPuter } from "../src/modules/PuterModule.js";
5
+ import { getCurrentDirectory } from "../src/commands/auth.js";
6
+
7
+ vi.mock("../src/commands/subdomains")
8
+ vi.mock("../src/modules/PuterModule")
9
+ vi.mock('../src/commands/auth.js');
10
+
11
+ vi.spyOn(console, "log").mockImplementation(() => { });
12
+
13
+ describe("listSites", () => {
14
+ it("should list sites successfully", async () => {
15
+ vi.mocked(getSubdomains).mockResolvedValue([{
16
+ uid: "123",
17
+ subdomain: "hehe.puter.site",
18
+ root_dir: { path: "/some/path" },
19
+ }])
20
+ await listSites();
21
+ expect(getSubdomains).toHaveBeenCalled();
22
+ expect(console.log).toHaveBeenCalledWith(expect.stringContaining("Total Sites: 1"))
23
+ })
24
+ })
25
+
26
+ describe("infoSite", () => {
27
+ it("should get site info successfully", async () => {
28
+ const mockHostingGet = vi.fn().mockResolvedValue({
29
+ uid: "123",
30
+ subdomain: "hehe.puter.site",
31
+ root_dir: { path: "/some/path" },
32
+ });
33
+ vi.mocked(getPuter).mockReturnValue({
34
+ hosting: {
35
+ get: mockHostingGet
36
+ }
37
+ })
38
+
39
+ await infoSite(["hehe.puter.site"])
40
+ expect(getPuter).toHaveBeenCalled();
41
+ expect(mockHostingGet).toHaveBeenCalled();
42
+ expect(console.log).toHaveBeenCalledWith(expect.stringContaining("hehe.puter.site"))
43
+ })
44
+ })
45
+
46
+ describe("deleteSite", () => {
47
+ it("should delete site successfully", async () => {
48
+ vi.mocked(deleteSubdomain)
49
+ const result = await deleteSite(["hehe.puter.site"]);
50
+ expect(result).toBe(true);
51
+ })
52
+ })
53
+
54
+ describe("createSite", () => {
55
+ it("should create site successfully", async () => {
56
+ vi.mocked(createSubdomain).mockResolvedValue({
57
+ uid: "123",
58
+ subdomain: "hehe.puter.site",
59
+ root_dir: { path: "/some/path" },
60
+ })
61
+ vi.mocked(getCurrentDirectory).mockReturnValue('/testuser');
62
+ const result = await createSite(["hehe hehe --subdomain=hehe"]);
63
+ expect(result).toMatchObject({
64
+ subdomain: "hehe.puter.site"
65
+ })
66
+ })
67
+ })
@@ -0,0 +1,90 @@
1
+ import { vi } from "vitest";
2
+ import { it } from "vitest";
3
+ import { describe } from "vitest";
4
+ import { createSubdomain, deleteSubdomain, getSubdomains, updateSubdomain } from "../src/commands/subdomains";
5
+ import { expect } from "vitest";
6
+ import { getPuter } from "../src/modules/PuterModule";
7
+
8
+ vi.mock("../src/modules/PuterModule")
9
+ vi.mock("conf", () => {
10
+ const Conf = vi.fn(() => ({
11
+ get: vi.fn(),
12
+ set: vi.fn(),
13
+ clear: vi.fn(),
14
+ }));
15
+ return { default: Conf };
16
+ });
17
+
18
+ vi.spyOn(console, "log").mockImplementation(() => { });
19
+
20
+ describe("getSubdomains", () => {
21
+ it("should get subdomains successfully", async () => {
22
+ const mockHostingList = vi.fn().mockResolvedValue([{
23
+ uid: "123",
24
+ subdomain: "hehe.puter.site",
25
+ root_dir: { path: "/some/path" },
26
+ }]);
27
+ vi.mocked(getPuter).mockReturnValue({
28
+ hosting: {
29
+ list: mockHostingList
30
+ }
31
+ })
32
+ const result = await getSubdomains();
33
+ expect(getPuter).toHaveBeenCalled();
34
+ expect(result).toHaveLength(1)
35
+ })
36
+ })
37
+
38
+ describe("deleteSubdomain", () => {
39
+ it("should delete subdomain successfully", async () => {
40
+ const mockHostingDelete = vi.fn().mockResolvedValue(true);
41
+ vi.mocked(getPuter).mockReturnValue({
42
+ hosting: {
43
+ delete: mockHostingDelete
44
+ }
45
+ })
46
+ const result = await deleteSubdomain(["hehe.puter.site"]);
47
+ expect(result).toBe(true);
48
+ expect(console.log).toHaveBeenCalledWith(expect.stringContaining("Subdomain deleted successfully"));
49
+ })
50
+ })
51
+
52
+ describe("createSubdomain", () => {
53
+ it("should create subdomain successfully", async () => {
54
+ const mockHostingCreate = vi.fn().mockResolvedValue({
55
+ uid: "123",
56
+ subdomain: "hehe.puter.site",
57
+ root_dir: { path: "/some/path" },
58
+ });
59
+ vi.mocked(getPuter).mockReturnValue({
60
+ hosting: {
61
+ create: mockHostingCreate
62
+ }
63
+ })
64
+ const result = await createSubdomain("hehe", "/mydir")
65
+ expect(result).toMatchObject({
66
+ subdomain: "hehe.puter.site"
67
+ })
68
+ })
69
+ })
70
+
71
+ describe("updateSubdomain", () => {
72
+ it("should update subdomain successfully", async () => {
73
+ const mockHostingUpdate = vi.fn().mockResolvedValue({
74
+ uid: "123",
75
+ subdomain: "hehe.puter.site",
76
+ root_dir: { path: "/newdir" },
77
+ });
78
+ vi.mocked(getPuter).mockReturnValue({
79
+ hosting: {
80
+ update: mockHostingUpdate
81
+ }
82
+ })
83
+ const result = await updateSubdomain("hehe", "/newdir")
84
+ expect(result).toMatchObject({
85
+ root_dir: {
86
+ path: "/newdir"
87
+ }
88
+ })
89
+ })
90
+ })
@@ -1,5 +0,0 @@
1
- import { set_context } from "../temporary/context_helpers.js";
2
-
3
- export default ({ context }) => {
4
- set_context(context);
5
- };
@@ -1,17 +0,0 @@
1
- /**
2
- * Ideally we would always get a context from the caller, but in some places
3
- * this is not possible without creating a large number of changes
4
- * (and therefore also a large number of new bugs). This temporary file
5
- * provides a way to get the context that modules get.
6
- */
7
-
8
- let context;
9
-
10
- // This is called by (list all callers so we can keep track):
11
- // - auth.js:getAuthToken
12
- export const get_context = () => {
13
- return context;
14
- };
15
-
16
- // This is called by SetContextModule
17
- export const set_context = ctx => context = ctx;