UNPKG

puter-cli

Version:

Command line interface for Puter cloud platform

381 lines (285 loc) 11.6 kB
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { getAuthToken } from '../src/commands/auth.js'; import { formatSize } from '../src/utils.js'; import { readFile } from 'fs/promises'; vi.mock('../src/commands/auth.js'); vi.mock('../src/utils.js'); vi.mock('fs/promises'); vi.mock('dotenv', () => ({ default: { config: vi.fn(), }, })); vi.spyOn(console, 'log').mockImplementation(() => { }); vi.spyOn(console, 'error').mockImplementation(() => { }); let commons; beforeEach(async () => { vi.resetModules(); vi.clearAllMocks(); vi.mocked(getAuthToken).mockReturnValue('mock-token'); vi.mocked(formatSize).mockImplementation((size) => `${size} bytes`); commons = await import('../src/commons.js'); }); describe('constants', () => { it('should export PROJECT_NAME', () => { expect(commons.PROJECT_NAME).toBe('puter-cli'); }); it('should export NULL_UUID', () => { expect(commons.NULL_UUID).toBe('00000000-0000-0000-0000-000000000000'); }); it('should have default API_BASE', () => { expect(commons.API_BASE).toBeDefined(); }); it('should have default BASE_URL', () => { expect(commons.BASE_URL).toBeDefined(); }); }); describe('reconfigureURLs', () => { it('should update API_BASE and BASE_URL', async () => { vi.resetModules(); const freshCommons = await import('../src/commons.js'); freshCommons.reconfigureURLs({ api: 'https://new-api.example.com', base: 'https://new.example.com' }); expect(freshCommons.API_BASE).toBe('https://new-api.example.com'); expect(freshCommons.BASE_URL).toBe('https://new.example.com'); }); }); describe('getHeaders', () => { it('should return headers with default content type', () => { const headers = commons.getHeaders(); expect(headers['Content-Type']).toBe('application/json'); expect(headers['Authorization']).toBe('Bearer mock-token'); expect(headers['Accept']).toBe('*/*'); expect(headers['Accept-Language']).toBe('en-US,en;q=0.9'); expect(headers['Connection']).toBe('keep-alive'); }); it('should return headers with custom content type', () => { const headers = commons.getHeaders('multipart/form-data'); expect(headers['Content-Type']).toBe('multipart/form-data'); }); it('should include Origin and Referer based on BASE_URL', async () => { vi.resetModules(); const freshCommons = await import('../src/commons.js'); freshCommons.reconfigureURLs({ api: 'https://api.test.com', base: 'https://test.com' }); const headers = freshCommons.getHeaders(); expect(headers['Origin']).toBe('https://test.com'); expect(headers['Referer']).toBe('https://test.com/'); }); }); describe('generateAppName', () => { it('should generate a name with default separator', () => { const name = commons.generateAppName(); expect(name).toMatch(/^[a-z]+-[a-z]+-\d+$/); }); it('should generate a name with custom separator', () => { const name = commons.generateAppName('_'); expect(name).toMatch(/^[a-z]+_[a-z]+_\d+$/); }); }); describe('displayTable', () => { it('should display a table with headers and data', () => { const consoleLogSpy = vi.spyOn(console, 'log'); const data = [ { name: 'App1', status: 'running' }, { name: 'App2', status: 'stopped' }, ]; commons.displayTable(data, { headers: ['Name', 'Status'], columns: ['name', 'status'], columnWidth: 15, }); expect(consoleLogSpy).toHaveBeenCalled(); }); it('should handle empty data', () => { const consoleLogSpy = vi.spyOn(console, 'log'); commons.displayTable([], { headers: ['Name'], columns: ['name'], }); expect(consoleLogSpy).toHaveBeenCalledTimes(2); // header + separator }); it('should display N/A for missing values', () => { const consoleLogSpy = vi.spyOn(console, 'log'); const data = [{ name: 'App1' }]; commons.displayTable(data, { headers: ['Name', 'Status'], columns: ['name', 'status'], columnWidth: 10, }); const calls = consoleLogSpy.mock.calls.flat(); expect(calls.some(call => call.includes('N/A'))).toBe(true); }); }); describe('showDiskSpaceUsage', () => { it('should display disk usage information', () => { const consoleLogSpy = vi.spyOn(console, 'log'); const data = { capacity: '1000000000', used: '500000000', }; commons.showDiskSpaceUsage(data); expect(consoleLogSpy).toHaveBeenCalled(); expect(formatSize).toHaveBeenCalledWith('1000000000'); // capacity expect(formatSize).toHaveBeenCalledWith('500000000'); // used expect(formatSize).toHaveBeenCalledWith(500000000); // free space }); it('should calculate usage percentage correctly', () => { const consoleLogSpy = vi.spyOn(console, 'log'); const data = { capacity: '100', used: '25', }; commons.showDiskSpaceUsage(data); const calls = consoleLogSpy.mock.calls.flat().join(' '); expect(calls).toContain('25.00%'); }); }); describe('resolvePath', () => { it('should resolve simple relative path', () => { expect(commons.resolvePath('/home/user', 'documents')).toBe('/home/user/documents'); }); it('should resolve parent directory', () => { expect(commons.resolvePath('/home/user/documents', '..')).toBe('/home/user'); }); it('should resolve current directory', () => { expect(commons.resolvePath('/home/user', '.')).toBe('/home/user'); }); it('should resolve multiple parent directories', () => { expect(commons.resolvePath('/home/user/documents/files', '../..')).toBe('/home/user'); }); it('should handle trailing slashes', () => { expect(commons.resolvePath('/home/user/', 'documents')).toBe('/home/user/documents'); }); it('should handle empty relative path', () => { expect(commons.resolvePath('/home/user', '')).toBe('/home/user'); }); it('should return root when going above root', () => { expect(commons.resolvePath('/home', '../../../..')).toBe('/'); }); it('should normalize duplicate slashes', () => { expect(commons.resolvePath('/home//user', 'documents')).toBe('/home/user/documents'); }); }); describe('resolveRemotePath', () => { it('should return absolute path as-is', () => { expect(commons.resolveRemotePath('/home/user', '/absolute/path')).toBe('/absolute/path'); }); it('should resolve relative path', () => { expect(commons.resolveRemotePath('/home/user', 'relative/path')).toBe('/home/user/relative/path'); }); }); describe('isValidAppName', () => { it('should return true for valid app name', () => { expect(commons.isValidAppName('my-app')).toBe(true); }); it('should return true for app name with spaces', () => { expect(commons.isValidAppName('my app')).toBe(true); }); it('should return false for empty string', () => { expect(commons.isValidAppName('')).toBe(false); }); it('should return false for whitespace only', () => { expect(commons.isValidAppName(' ')).toBe(false); }); it('should return false for reserved name "."', () => { expect(commons.isValidAppName('.')).toBe(false); }); it('should return false for reserved name ".."', () => { expect(commons.isValidAppName('..')).toBe(false); }); it('should return false for name with forward slash', () => { expect(commons.isValidAppName('my/app')).toBe(false); }); it('should return false for name with backslash', () => { expect(commons.isValidAppName('my\\app')).toBe(false); }); it('should return false for name with wildcard', () => { expect(commons.isValidAppName('my*app')).toBe(false); }); it('should return false for non-string input', () => { expect(commons.isValidAppName(123)).toBe(false); expect(commons.isValidAppName(null)).toBe(false); expect(commons.isValidAppName(undefined)).toBe(false); }); }); describe('getDefaultHomePage', () => { it('should generate HTML with app name', () => { const html = commons.getDefaultHomePage('TestApp'); expect(html).toContain('<title>TestApp</title>'); expect(html).toContain('Welcome to TestApp!'); }); it('should include CSS files when provided', () => { const html = commons.getDefaultHomePage('TestApp', [], ['style.css', 'theme.css']); expect(html).toContain('<link href="style.css" rel="stylesheet">'); expect(html).toContain('<link href="theme.css" rel="stylesheet">'); }); it('should include JS files when provided', () => { const html = commons.getDefaultHomePage('TestApp', ['app.js', 'utils.js']); expect(html).toContain('<script type="text/babel" src="app.js"></script>'); expect(html).toContain('<script src="utils.js"></script>'); }); it('should use id="root" when react is included', () => { const html = commons.getDefaultHomePage('TestApp', ['react.js']); expect(html).toContain('id="root"'); }); it('should use id="app" when no react', () => { const html = commons.getDefaultHomePage('TestApp', ['vanilla.js']); expect(html).toContain('id="app"'); }); }); describe('getVersionFromPackage', () => { it('should return version from package.json', async () => { vi.mocked(readFile).mockResolvedValueOnce(JSON.stringify({ version: '1.2.3' })); const version = await commons.getVersionFromPackage(); expect(version).toBe('1.2.3'); }); it('should fallback to production package.json on dev error', async () => { vi.mocked(readFile) .mockRejectedValueOnce(new Error('File not found')) .mockResolvedValueOnce(JSON.stringify({ version: '2.0.0' })); const version = await commons.getVersionFromPackage(); expect(version).toBe('2.0.0'); }); it('should return null on error', async () => { vi.mocked(readFile).mockRejectedValue(new Error('Read error')); const version = await commons.getVersionFromPackage(); expect(version).toBeNull(); }); }); describe('getLatestVersion', () => { beforeEach(() => { global.fetch = vi.fn(); }); it('should return up-to-date status when versions match', async () => { vi.mocked(readFile).mockResolvedValueOnce(JSON.stringify({ version: '1.0.0' })); vi.mocked(global.fetch).mockResolvedValueOnce({ ok: true, json: () => Promise.resolve({ version: '1.0.0' }), }); const result = await commons.getLatestVersion('puter-cli'); expect(result).toBe('v1.0.0 (up-to-date)'); }); it('should return latest version when different', async () => { vi.mocked(readFile).mockResolvedValueOnce(JSON.stringify({ version: '1.0.0' })); vi.mocked(global.fetch).mockResolvedValueOnce({ ok: true, json: () => Promise.resolve({ version: '2.0.0' }), }); const result = await commons.getLatestVersion('puter-cli'); expect(result).toBe('v1.0.0 (latest: 2.0.0)'); }); it('should return offline status when fetch fails', async () => { vi.mocked(readFile).mockResolvedValueOnce(JSON.stringify({ version: '1.0.0' })); vi.mocked(global.fetch).mockRejectedValueOnce(new Error('Network error')); const result = await commons.getLatestVersion('puter-cli'); expect(result).toBe('v1.0.0 (offline)'); }); it('should handle unknown current version', async () => { vi.mocked(readFile).mockRejectedValue(new Error('Read error')); vi.mocked(global.fetch).mockResolvedValueOnce({ ok: true, json: () => Promise.resolve({ version: '2.0.0' }), }); const result = await commons.getLatestVersion('puter-cli'); expect(result).toBe('vunknown (latest: 2.0.0)'); }); });