Skip to content

Commit 1812cad

Browse files
committed
test(apps): add unit tests for apps command
1 parent b1a640d commit 1812cad

File tree

1 file changed

+194
-0
lines changed

1 file changed

+194
-0
lines changed

tests/apps.test.js

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
import { describe, it, expect, vi, beforeEach } from 'vitest';
2+
import { listApps, appInfo, createApp, updateApp, deleteApp } from '../src/commands/apps.js';
3+
import chalk from 'chalk';
4+
import Table from 'cli-table3';
5+
import * as PuterModule from '../src/modules/PuterModule.js';
6+
import * as subdomains from '../src/commands/subdomains.js';
7+
import * as sites from '../src/commands/sites.js';
8+
import * as files from '../src/commands/files.js';
9+
import * as auth from '../src/commands/auth.js';
10+
import * as commons from '../src/commons.js';
11+
import * as utils from '../src/utils.js';
12+
import crypto from '../src/crypto.js';
13+
14+
// Mock console to prevent actual logging
15+
vi.spyOn(console, 'log').mockImplementation(() => {});
16+
vi.spyOn(console, 'error').mockImplementation(() => {});
17+
18+
vi.mock("conf", () => {
19+
const Conf = vi.fn(() => ({
20+
get: vi.fn(),
21+
set: vi.fn(),
22+
clear: vi.fn(),
23+
}));
24+
return { default: Conf };
25+
});
26+
27+
// Mock dependencies
28+
vi.mock('chalk', () => ({
29+
default: {
30+
green: vi.fn(text => text),
31+
red: vi.fn(text => text),
32+
dim: vi.fn(text => text),
33+
yellow: vi.fn(text => text),
34+
cyan: vi.fn(text => text),
35+
cyanBright: vi.fn(text => text),
36+
bold: vi.fn(text => text),
37+
}
38+
}));
39+
vi.mock('cli-table3');
40+
vi.mock('node-fetch');
41+
vi.mock('../src/modules/PuterModule.js');
42+
vi.mock('../src/commands/subdomains.js');
43+
vi.mock('../src/commands/sites.js');
44+
vi.mock('../src/commands/files.js');
45+
vi.mock('../src/commands/auth.js');
46+
vi.mock('../src/commons.js');
47+
vi.mock('../src/utils.js');
48+
vi.mock('../src/crypto.js');
49+
50+
const mockPuter = {
51+
apps: {
52+
list: vi.fn(),
53+
get: vi.fn(),
54+
create: vi.fn(),
55+
update: vi.fn(),
56+
delete: vi.fn(),
57+
},
58+
fs: {
59+
mkdir: vi.fn(),
60+
},
61+
};
62+
63+
describe('apps.js', () => {
64+
let mockTable;
65+
66+
beforeEach(() => {
67+
vi.clearAllMocks();
68+
vi.spyOn(PuterModule, 'getPuter').mockReturnValue(mockPuter);
69+
vi.spyOn(auth, 'getCurrentDirectory').mockReturnValue('/testuser');
70+
vi.spyOn(commons, 'isValidAppName').mockReturnValue(true);
71+
vi.spyOn(commons, 'resolvePath').mockImplementation((_, newPath) => newPath);
72+
vi.spyOn(crypto, 'randomUUID').mockReturnValue('mock-uuid');
73+
74+
mockTable = {
75+
push: vi.fn(),
76+
toString: vi.fn(() => 'table string'),
77+
};
78+
Table.mockImplementation(() => mockTable);
79+
});
80+
81+
describe('listApps', () => {
82+
it('should list apps successfully', async () => {
83+
const mockApps = [
84+
{ title: 'App 1', name: 'app-1', created_at: new Date().toISOString(), index_url: 'https://app-1.puter.site', stats: { open_count: 10, user_count: 5 } },
85+
{ title: 'App 2', name: 'app-2', created_at: new Date().toISOString(), index_url: 'https://app-2.puter.site', stats: { open_count: 20, user_count: 15 } },
86+
];
87+
mockPuter.apps.list.mockResolvedValue(mockApps);
88+
vi.spyOn(utils, 'formatDate').mockReturnValue('formatted-date');
89+
90+
await listApps();
91+
92+
expect(mockPuter.apps.list).toHaveBeenCalled();
93+
expect(mockTable.push).toHaveBeenCalledTimes(2);
94+
expect(console.log).toHaveBeenCalledWith('table string');
95+
});
96+
97+
it('should handle API error when listing apps', async () => {
98+
mockPuter.apps.list.mockRejectedValue(new Error('API Error'));
99+
await listApps();
100+
expect(console.error).toHaveBeenCalledWith('Failed to list apps. Error: API Error');
101+
});
102+
});
103+
104+
describe('appInfo', () => {
105+
it('should show app info successfully', async () => {
106+
const mockApp = { name: 'test-app', title: 'Test App' };
107+
mockPuter.apps.get.mockResolvedValue(mockApp);
108+
vi.spyOn(utils, 'displayNonNullValues').mockImplementation(() => {});
109+
110+
await appInfo(['test-app']);
111+
112+
expect(mockPuter.apps.get).toHaveBeenCalledWith('test-app');
113+
expect(utils.displayNonNullValues).toHaveBeenCalledWith(mockApp);
114+
});
115+
116+
it('should show usage if no app name is provided', async () => {
117+
await appInfo([]);
118+
expect(console.log).toHaveBeenCalledWith(chalk.red('Usage: app <name>'));
119+
});
120+
121+
it('should handle app not found', async () => {
122+
mockPuter.apps.get.mockResolvedValue(null);
123+
await appInfo(['non-existent-app']);
124+
expect(console.error).toHaveBeenCalledWith(chalk.red('Could not find this app.'));
125+
});
126+
});
127+
128+
describe('createApp', () => {
129+
beforeEach(() => {
130+
mockPuter.apps.create.mockResolvedValue({ uid: 'app-uid', name: 'new-app', owner: { username: 'testuser' } });
131+
mockPuter.fs.mkdir.mockResolvedValue({ uid: 'dir-uid', name: 'app-mock-uuid' });
132+
vi.spyOn(subdomains, 'createSubdomain').mockResolvedValue(true);
133+
vi.spyOn(files, 'createFile').mockResolvedValue(true);
134+
mockPuter.apps.update.mockResolvedValue(true);
135+
})
136+
it('should create an app successfully', async () => {
137+
await createApp({ name: 'new-app' });
138+
139+
expect(mockPuter.apps.create).toHaveBeenCalled();
140+
expect(mockPuter.fs.mkdir).toHaveBeenCalled();
141+
expect(subdomains.createSubdomain).toHaveBeenCalled();
142+
expect(files.createFile).toHaveBeenCalled();
143+
expect(mockPuter.apps.update).toHaveBeenCalled();
144+
expect(console.log).toHaveBeenCalledWith(chalk.green('App deployed successfully at:'));
145+
});
146+
147+
it('should show usage if app name is invalid', async () => {
148+
vi.spyOn(commons, 'isValidAppName').mockReturnValue(false);
149+
await createApp({ name: 'invalid-' });
150+
expect(console.log).toHaveBeenCalledWith(chalk.red('Usage: app:create <name> <directory>'));
151+
});
152+
});
153+
154+
describe('updateApp', () => {
155+
it('should update an app successfully', async () => {
156+
mockPuter.apps.get.mockResolvedValue({ uid: 'app-uid', name: 'test-app', owner: { username: 'testuser' }, index_url: 'https://test.puter.site' });
157+
vi.spyOn(files, 'pathExists').mockResolvedValue(true);
158+
vi.spyOn(subdomains, 'getSubdomains').mockResolvedValue([{ root_dir: { dirname: 'app-uid', path: '/path/to/app' }, uid: 'sub-uid' }]);
159+
vi.spyOn(files, 'listRemoteFiles').mockResolvedValue([{ name: 'index.html' }]);
160+
vi.spyOn(files, 'copyFile').mockResolvedValue(true);
161+
vi.spyOn(files, 'removeFileOrDirectory').mockResolvedValue(true);
162+
163+
await updateApp(['test-app', '.']);
164+
165+
expect(mockPuter.apps.get).toHaveBeenCalledWith('test-app');
166+
expect(files.listRemoteFiles).toHaveBeenCalled();
167+
expect(files.copyFile).toHaveBeenCalled();
168+
expect(console.log).toHaveBeenCalledWith(chalk.green('App updated successfully at:'));
169+
});
170+
});
171+
172+
describe('deleteApp', () => {
173+
it('should delete an app successfully', async () => {
174+
mockPuter.apps.get.mockResolvedValue({ uid: 'app-uid', name: 'test-app', title: 'Test App', created_at: new Date().toISOString() });
175+
mockPuter.apps.delete.mockResolvedValue(true);
176+
vi.spyOn(subdomains, 'getSubdomains').mockResolvedValue([{ root_dir: { dirname: 'app-uid' }, uid: 'sub-uid' }]);
177+
vi.spyOn(sites, 'deleteSite').mockResolvedValue(true);
178+
179+
const result = await deleteApp('test-app');
180+
181+
expect(result).toBe(true);
182+
expect(mockPuter.apps.delete).toHaveBeenCalledWith('test-app');
183+
expect(sites.deleteSite).toHaveBeenCalled();
184+
expect(console.log).toHaveBeenCalledWith(chalk.green('App "test-app" deleted successfully!'));
185+
});
186+
187+
it('should return false if app not found', async () => {
188+
mockPuter.apps.get.mockResolvedValue(null);
189+
const result = await deleteApp('non-existent-app');
190+
expect(result).toBe(false);
191+
expect(console.log).toHaveBeenCalledWith(chalk.red('App "non-existent-app" not found.'));
192+
});
193+
});
194+
});

0 commit comments

Comments
 (0)