Skip to content

Commit b4b8131

Browse files
authored
Merge pull request #64 from reynaldichernando/test-commons
Add commons.test.js
2 parents 9e37490 + 268b21d commit b4b8131

File tree

1 file changed

+380
-0
lines changed

1 file changed

+380
-0
lines changed

tests/commons.test.js

Lines changed: 380 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,380 @@
1+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2+
import { getAuthToken } from '../src/commands/auth.js';
3+
import { formatSize } from '../src/utils.js';
4+
import { readFile } from 'fs/promises';
5+
6+
vi.mock('../src/commands/auth.js');
7+
vi.mock('../src/utils.js');
8+
vi.mock('fs/promises');
9+
vi.mock('dotenv', () => ({
10+
default: {
11+
config: vi.fn(),
12+
},
13+
}));
14+
15+
vi.spyOn(console, 'log').mockImplementation(() => { });
16+
vi.spyOn(console, 'error').mockImplementation(() => { });
17+
18+
let commons;
19+
20+
beforeEach(async () => {
21+
vi.resetModules();
22+
vi.clearAllMocks();
23+
24+
vi.mocked(getAuthToken).mockReturnValue('mock-token');
25+
vi.mocked(formatSize).mockImplementation((size) => `${size} bytes`);
26+
27+
commons = await import('../src/commons.js');
28+
});
29+
30+
describe('constants', () => {
31+
it('should export PROJECT_NAME', () => {
32+
expect(commons.PROJECT_NAME).toBe('puter-cli');
33+
});
34+
35+
it('should export NULL_UUID', () => {
36+
expect(commons.NULL_UUID).toBe('00000000-0000-0000-0000-000000000000');
37+
});
38+
39+
it('should have default API_BASE', () => {
40+
expect(commons.API_BASE).toBeDefined();
41+
});
42+
43+
it('should have default BASE_URL', () => {
44+
expect(commons.BASE_URL).toBeDefined();
45+
});
46+
});
47+
48+
describe('reconfigureURLs', () => {
49+
it('should update API_BASE and BASE_URL', async () => {
50+
vi.resetModules();
51+
const freshCommons = await import('../src/commons.js');
52+
53+
freshCommons.reconfigureURLs({ api: 'https://new-api.example.com', base: 'https://new.example.com' });
54+
55+
expect(freshCommons.API_BASE).toBe('https://new-api.example.com');
56+
expect(freshCommons.BASE_URL).toBe('https://new.example.com');
57+
});
58+
});
59+
60+
describe('getHeaders', () => {
61+
it('should return headers with default content type', () => {
62+
const headers = commons.getHeaders();
63+
64+
expect(headers['Content-Type']).toBe('application/json');
65+
expect(headers['Authorization']).toBe('Bearer mock-token');
66+
expect(headers['Accept']).toBe('*/*');
67+
expect(headers['Accept-Language']).toBe('en-US,en;q=0.9');
68+
expect(headers['Connection']).toBe('keep-alive');
69+
});
70+
71+
it('should return headers with custom content type', () => {
72+
const headers = commons.getHeaders('multipart/form-data');
73+
74+
expect(headers['Content-Type']).toBe('multipart/form-data');
75+
});
76+
77+
it('should include Origin and Referer based on BASE_URL', async () => {
78+
vi.resetModules();
79+
const freshCommons = await import('../src/commons.js');
80+
freshCommons.reconfigureURLs({ api: 'https://api.test.com', base: 'https://test.com' });
81+
82+
const headers = freshCommons.getHeaders();
83+
84+
expect(headers['Origin']).toBe('https://test.com');
85+
expect(headers['Referer']).toBe('https://test.com/');
86+
});
87+
});
88+
89+
describe('generateAppName', () => {
90+
it('should generate a name with default separator', () => {
91+
const name = commons.generateAppName();
92+
93+
expect(name).toMatch(/^[a-z]+-[a-z]+-\d+$/);
94+
});
95+
96+
it('should generate a name with custom separator', () => {
97+
const name = commons.generateAppName('_');
98+
99+
expect(name).toMatch(/^[a-z]+_[a-z]+_\d+$/);
100+
});
101+
});
102+
103+
describe('displayTable', () => {
104+
it('should display a table with headers and data', () => {
105+
const consoleLogSpy = vi.spyOn(console, 'log');
106+
107+
const data = [
108+
{ name: 'App1', status: 'running' },
109+
{ name: 'App2', status: 'stopped' },
110+
];
111+
112+
commons.displayTable(data, {
113+
headers: ['Name', 'Status'],
114+
columns: ['name', 'status'],
115+
columnWidth: 15,
116+
});
117+
118+
expect(consoleLogSpy).toHaveBeenCalled();
119+
});
120+
121+
it('should handle empty data', () => {
122+
const consoleLogSpy = vi.spyOn(console, 'log');
123+
124+
commons.displayTable([], {
125+
headers: ['Name'],
126+
columns: ['name'],
127+
});
128+
129+
expect(consoleLogSpy).toHaveBeenCalledTimes(2); // header + separator
130+
});
131+
132+
it('should display N/A for missing values', () => {
133+
const consoleLogSpy = vi.spyOn(console, 'log');
134+
135+
const data = [{ name: 'App1' }];
136+
137+
commons.displayTable(data, {
138+
headers: ['Name', 'Status'],
139+
columns: ['name', 'status'],
140+
columnWidth: 10,
141+
});
142+
143+
const calls = consoleLogSpy.mock.calls.flat();
144+
expect(calls.some(call => call.includes('N/A'))).toBe(true);
145+
});
146+
});
147+
148+
describe('showDiskSpaceUsage', () => {
149+
it('should display disk usage information', () => {
150+
const consoleLogSpy = vi.spyOn(console, 'log');
151+
152+
const data = {
153+
capacity: '1000000000',
154+
used: '500000000',
155+
};
156+
157+
commons.showDiskSpaceUsage(data);
158+
159+
expect(consoleLogSpy).toHaveBeenCalled();
160+
expect(formatSize).toHaveBeenCalledWith('1000000000'); // capacity
161+
expect(formatSize).toHaveBeenCalledWith('500000000'); // used
162+
expect(formatSize).toHaveBeenCalledWith(500000000); // free space
163+
});
164+
165+
it('should calculate usage percentage correctly', () => {
166+
const consoleLogSpy = vi.spyOn(console, 'log');
167+
168+
const data = {
169+
capacity: '100',
170+
used: '25',
171+
};
172+
173+
commons.showDiskSpaceUsage(data);
174+
175+
const calls = consoleLogSpy.mock.calls.flat().join(' ');
176+
expect(calls).toContain('25.00%');
177+
});
178+
});
179+
180+
describe('resolvePath', () => {
181+
it('should resolve simple relative path', () => {
182+
expect(commons.resolvePath('/home/user', 'documents')).toBe('/home/user/documents');
183+
});
184+
185+
it('should resolve parent directory', () => {
186+
expect(commons.resolvePath('/home/user/documents', '..')).toBe('/home/user');
187+
});
188+
189+
it('should resolve current directory', () => {
190+
expect(commons.resolvePath('/home/user', '.')).toBe('/home/user');
191+
});
192+
193+
it('should resolve multiple parent directories', () => {
194+
expect(commons.resolvePath('/home/user/documents/files', '../..')).toBe('/home/user');
195+
});
196+
197+
it('should handle trailing slashes', () => {
198+
expect(commons.resolvePath('/home/user/', 'documents')).toBe('/home/user/documents');
199+
});
200+
201+
it('should handle empty relative path', () => {
202+
expect(commons.resolvePath('/home/user', '')).toBe('/home/user');
203+
});
204+
205+
it('should return root when going above root', () => {
206+
expect(commons.resolvePath('/home', '../../../..')).toBe('/');
207+
});
208+
209+
it('should normalize duplicate slashes', () => {
210+
expect(commons.resolvePath('/home//user', 'documents')).toBe('/home/user/documents');
211+
});
212+
});
213+
214+
describe('resolveRemotePath', () => {
215+
it('should return absolute path as-is', () => {
216+
expect(commons.resolveRemotePath('/home/user', '/absolute/path')).toBe('/absolute/path');
217+
});
218+
219+
it('should resolve relative path', () => {
220+
expect(commons.resolveRemotePath('/home/user', 'relative/path')).toBe('/home/user/relative/path');
221+
});
222+
});
223+
224+
describe('isValidAppName', () => {
225+
it('should return true for valid app name', () => {
226+
expect(commons.isValidAppName('my-app')).toBe(true);
227+
});
228+
229+
it('should return true for app name with spaces', () => {
230+
expect(commons.isValidAppName('my app')).toBe(true);
231+
});
232+
233+
it('should return false for empty string', () => {
234+
expect(commons.isValidAppName('')).toBe(false);
235+
});
236+
237+
it('should return false for whitespace only', () => {
238+
expect(commons.isValidAppName(' ')).toBe(false);
239+
});
240+
241+
it('should return false for reserved name "."', () => {
242+
expect(commons.isValidAppName('.')).toBe(false);
243+
});
244+
245+
it('should return false for reserved name ".."', () => {
246+
expect(commons.isValidAppName('..')).toBe(false);
247+
});
248+
249+
it('should return false for name with forward slash', () => {
250+
expect(commons.isValidAppName('my/app')).toBe(false);
251+
});
252+
253+
it('should return false for name with backslash', () => {
254+
expect(commons.isValidAppName('my\\app')).toBe(false);
255+
});
256+
257+
it('should return false for name with wildcard', () => {
258+
expect(commons.isValidAppName('my*app')).toBe(false);
259+
});
260+
261+
it('should return false for non-string input', () => {
262+
expect(commons.isValidAppName(123)).toBe(false);
263+
expect(commons.isValidAppName(null)).toBe(false);
264+
expect(commons.isValidAppName(undefined)).toBe(false);
265+
});
266+
});
267+
268+
describe('getDefaultHomePage', () => {
269+
it('should generate HTML with app name', () => {
270+
const html = commons.getDefaultHomePage('TestApp');
271+
272+
expect(html).toContain('<title>TestApp</title>');
273+
expect(html).toContain('Welcome to TestApp!');
274+
});
275+
276+
it('should include CSS files when provided', () => {
277+
const html = commons.getDefaultHomePage('TestApp', [], ['style.css', 'theme.css']);
278+
279+
expect(html).toContain('<link href="style.css" rel="stylesheet">');
280+
expect(html).toContain('<link href="theme.css" rel="stylesheet">');
281+
});
282+
283+
it('should include JS files when provided', () => {
284+
const html = commons.getDefaultHomePage('TestApp', ['app.js', 'utils.js']);
285+
286+
expect(html).toContain('<script type="text/babel" src="app.js"></script>');
287+
expect(html).toContain('<script src="utils.js"></script>');
288+
});
289+
290+
it('should use id="root" when react is included', () => {
291+
const html = commons.getDefaultHomePage('TestApp', ['react.js']);
292+
293+
expect(html).toContain('id="root"');
294+
});
295+
296+
it('should use id="app" when no react', () => {
297+
const html = commons.getDefaultHomePage('TestApp', ['vanilla.js']);
298+
299+
expect(html).toContain('id="app"');
300+
});
301+
});
302+
303+
describe('getVersionFromPackage', () => {
304+
it('should return version from package.json', async () => {
305+
vi.mocked(readFile).mockResolvedValueOnce(JSON.stringify({ version: '1.2.3' }));
306+
307+
const version = await commons.getVersionFromPackage();
308+
309+
expect(version).toBe('1.2.3');
310+
});
311+
312+
it('should fallback to production package.json on dev error', async () => {
313+
vi.mocked(readFile)
314+
.mockRejectedValueOnce(new Error('File not found'))
315+
.mockResolvedValueOnce(JSON.stringify({ version: '2.0.0' }));
316+
317+
const version = await commons.getVersionFromPackage();
318+
319+
expect(version).toBe('2.0.0');
320+
});
321+
322+
it('should return null on error', async () => {
323+
vi.mocked(readFile).mockRejectedValue(new Error('Read error'));
324+
325+
const version = await commons.getVersionFromPackage();
326+
327+
expect(version).toBeNull();
328+
});
329+
});
330+
331+
describe('getLatestVersion', () => {
332+
beforeEach(() => {
333+
global.fetch = vi.fn();
334+
});
335+
336+
it('should return up-to-date status when versions match', async () => {
337+
vi.mocked(readFile).mockResolvedValueOnce(JSON.stringify({ version: '1.0.0' }));
338+
vi.mocked(global.fetch).mockResolvedValueOnce({
339+
ok: true,
340+
json: () => Promise.resolve({ version: '1.0.0' }),
341+
});
342+
343+
const result = await commons.getLatestVersion('puter-cli');
344+
345+
expect(result).toBe('v1.0.0 (up-to-date)');
346+
});
347+
348+
it('should return latest version when different', async () => {
349+
vi.mocked(readFile).mockResolvedValueOnce(JSON.stringify({ version: '1.0.0' }));
350+
vi.mocked(global.fetch).mockResolvedValueOnce({
351+
ok: true,
352+
json: () => Promise.resolve({ version: '2.0.0' }),
353+
});
354+
355+
const result = await commons.getLatestVersion('puter-cli');
356+
357+
expect(result).toBe('v1.0.0 (latest: 2.0.0)');
358+
});
359+
360+
it('should return offline status when fetch fails', async () => {
361+
vi.mocked(readFile).mockResolvedValueOnce(JSON.stringify({ version: '1.0.0' }));
362+
vi.mocked(global.fetch).mockRejectedValueOnce(new Error('Network error'));
363+
364+
const result = await commons.getLatestVersion('puter-cli');
365+
366+
expect(result).toBe('v1.0.0 (offline)');
367+
});
368+
369+
it('should handle unknown current version', async () => {
370+
vi.mocked(readFile).mockRejectedValue(new Error('Read error'));
371+
vi.mocked(global.fetch).mockResolvedValueOnce({
372+
ok: true,
373+
json: () => Promise.resolve({ version: '2.0.0' }),
374+
});
375+
376+
const result = await commons.getLatestVersion('puter-cli');
377+
378+
expect(result).toBe('vunknown (latest: 2.0.0)');
379+
});
380+
});

0 commit comments

Comments
 (0)