| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480 |
- /**
- * @fileoverview Cross-Platform Compatibility Tests
- * @author GrabZilla Development Team
- * @version 2.1.0
- * @since 2024-01-01
- */
- import { describe, it, expect, beforeEach, vi } from 'vitest';
- import os from 'os';
- import path from 'path';
- import fs from 'fs';
- /**
- * CROSS-PLATFORM COMPATIBILITY TESTS
- *
- * These tests verify that the application works correctly across:
- * - macOS (darwin)
- * - Windows (win32)
- * - Linux (linux)
- *
- * Testing areas:
- * - Binary path resolution
- * - File system operations
- * - Path handling
- * - Process spawning
- * - Platform-specific features
- */
- describe('Cross-Platform Compatibility', () => {
- let currentPlatform;
- let mockPlatformInfo;
- beforeEach(() => {
- currentPlatform = process.platform;
- mockPlatformInfo = {
- platform: currentPlatform,
- arch: process.arch,
- homedir: os.homedir(),
- tmpdir: os.tmpdir(),
- pathSep: path.sep
- };
- });
- describe('Platform Detection and Binary Paths', () => {
- it('should detect current platform correctly', () => {
- const supportedPlatforms = ['darwin', 'win32', 'linux'];
- expect(supportedPlatforms).toContain(currentPlatform);
- });
- it('should resolve correct binary paths for each platform', () => {
- const getBinaryPath = (binaryName, platform = currentPlatform) => {
- const extension = platform === 'win32' ? '.exe' : '';
- return path.join('binaries', `${binaryName}${extension}`);
- };
- // Test for current platform
- const ytDlpPath = getBinaryPath('yt-dlp');
- const ffmpegPath = getBinaryPath('ffmpeg');
- if (currentPlatform === 'win32') {
- expect(ytDlpPath).toBe('binaries\\yt-dlp.exe');
- expect(ffmpegPath).toBe('binaries\\ffmpeg.exe');
- } else {
- expect(ytDlpPath).toBe('binaries/yt-dlp');
- expect(ffmpegPath).toBe('binaries/ffmpeg');
- }
- // Test for all platforms
- expect(getBinaryPath('yt-dlp', 'win32')).toMatch(/\.exe$/);
- expect(getBinaryPath('yt-dlp', 'darwin')).not.toMatch(/\.exe$/);
- expect(getBinaryPath('yt-dlp', 'linux')).not.toMatch(/\.exe$/);
- });
- it('should handle platform-specific path separators', () => {
- const createPath = (...segments) => path.join(...segments);
- const testPath = createPath('downloads', 'videos', 'test.mp4');
-
- if (currentPlatform === 'win32') {
- expect(testPath).toMatch(/\\/);
- } else {
- expect(testPath).toMatch(/\//);
- }
- });
- it('should resolve home directory correctly on all platforms', () => {
- const homeDir = os.homedir();
-
- expect(homeDir).toBeTruthy();
- expect(path.isAbsolute(homeDir)).toBe(true);
-
- // Platform-specific home directory patterns
- if (currentPlatform === 'win32') {
- expect(homeDir).toMatch(/^[A-Z]:\\/);
- } else {
- expect(homeDir).toMatch(/^\/.*\/[^/]+$/);
- }
- });
- });
- describe('File System Operations', () => {
- it('should handle file paths with different separators', () => {
- // Use path.join for cross-platform compatibility
- const testPaths = [
- path.join('downloads', 'video.mp4'),
- path.join('downloads', 'subfolder', 'video.mp4')
- ];
- testPaths.forEach(testPath => {
- const normalized = path.normalize(testPath);
- expect(normalized).toBeTruthy();
- const parsed = path.parse(normalized);
- expect(parsed.name).toBe('video');
- expect(parsed.ext).toBe('.mp4');
- // The base name should be consistent regardless of path separators
- expect(parsed.base).toBe('video.mp4');
- // Verify the path contains the expected directory
- expect(normalized).toMatch(/downloads/);
- });
- });
- it('should create directories with correct permissions', () => {
- const testDir = path.join(os.tmpdir(), 'grabzilla-test-' + Date.now());
-
- try {
- fs.mkdirSync(testDir, { recursive: true });
- expect(fs.existsSync(testDir)).toBe(true);
-
- const stats = fs.statSync(testDir);
- expect(stats.isDirectory()).toBe(true);
-
- // Check permissions (Unix-like systems)
- if (currentPlatform !== 'win32') {
- expect(stats.mode & 0o777).toBeGreaterThan(0);
- }
- } finally {
- // Cleanup
- if (fs.existsSync(testDir)) {
- fs.rmSync(testDir, { recursive: true, force: true });
- }
- }
- });
- it('should handle long file paths appropriately', () => {
- const longFileName = 'a'.repeat(200) + '.mp4';
- const longPath = path.join(os.tmpdir(), longFileName);
-
- // Windows has path length limitations
- if (currentPlatform === 'win32') {
- expect(longPath.length).toBeLessThan(260); // Windows MAX_PATH
- }
-
- // Test path parsing with long names
- const parsed = path.parse(longPath);
- expect(parsed.ext).toBe('.mp4');
- });
- it('should handle special characters in file names', () => {
- const specialChars = {
- 'win32': ['<', '>', ':', '"', '|', '?', '*'],
- 'darwin': [':'],
- 'linux': []
- };
- const invalidChars = specialChars[currentPlatform] || [];
-
- const testFileName = 'test_video_with_special_chars.mp4';
-
- // Verify our test filename doesn't contain invalid characters
- invalidChars.forEach(char => {
- expect(testFileName).not.toContain(char);
- });
-
- // Test sanitization function
- const sanitizeFileName = (name) => {
- let sanitized = name;
- invalidChars.forEach(char => {
- sanitized = sanitized.replace(new RegExp(`\\${char}`, 'g'), '_');
- });
- return sanitized;
- };
- const dirtyName = 'test<video>file.mp4';
- const cleanName = sanitizeFileName(dirtyName);
-
- if (currentPlatform === 'win32') {
- expect(cleanName).toBe('test_video_file.mp4');
- } else {
- expect(cleanName).toBe(dirtyName); // No changes needed on Unix-like systems
- }
- });
- });
- describe('Process Management', () => {
- it('should handle process spawning correctly on all platforms', () => {
- const getShellCommand = () => {
- switch (currentPlatform) {
- case 'win32':
- return { cmd: 'cmd', args: ['/c', 'echo', 'test'] };
- default:
- return { cmd: 'echo', args: ['test'] };
- }
- };
- const { cmd, args } = getShellCommand();
- expect(cmd).toBeTruthy();
- expect(Array.isArray(args)).toBe(true);
- });
- it('should handle process termination signals correctly', () => {
- const getTerminationSignal = () => {
- return currentPlatform === 'win32' ? 'SIGTERM' : 'SIGTERM';
- };
- const signal = getTerminationSignal();
- expect(['SIGTERM', 'SIGKILL', 'SIGINT']).toContain(signal);
- });
- it('should handle environment variables correctly', () => {
- const pathVar = process.env.PATH || process.env.Path;
- expect(pathVar).toBeTruthy();
-
- const pathSeparator = currentPlatform === 'win32' ? ';' : ':';
- const paths = pathVar.split(pathSeparator);
- expect(paths.length).toBeGreaterThan(0);
- });
- });
- describe('Platform-Specific Features', () => {
- it('should handle macOS-specific features', () => {
- if (currentPlatform === 'darwin') {
- // Test macOS-specific paths
- const applicationsPath = '/Applications';
- expect(fs.existsSync(applicationsPath)).toBe(true);
-
- // Test bundle handling
- const bundleExtensions = ['.app', '.bundle'];
- bundleExtensions.forEach(ext => {
- expect(ext.startsWith('.')).toBe(true);
- });
- } else {
- // Skip macOS-specific tests on other platforms
- expect(currentPlatform).not.toBe('darwin');
- }
- });
- it('should handle Windows-specific features', () => {
- if (currentPlatform === 'win32') {
- // Test Windows-specific paths
- const systemRoot = process.env.SystemRoot;
- expect(systemRoot).toBeTruthy();
- expect(systemRoot).toMatch(/^[A-Z]:\\/);
-
- // Test executable extensions
- const executableExtensions = ['.exe', '.bat', '.cmd'];
- executableExtensions.forEach(ext => {
- expect(ext.startsWith('.')).toBe(true);
- });
- } else {
- // Skip Windows-specific tests on other platforms
- expect(currentPlatform).not.toBe('win32');
- }
- });
- it('should handle Linux-specific features', () => {
- if (currentPlatform === 'linux') {
- // Test Linux-specific paths
- const homeDir = os.homedir();
- expect(homeDir).toMatch(/^\/home\/|^\/root$/);
-
- // Test executable permissions
- const executableMode = 0o755;
- expect(executableMode & 0o111).toBeGreaterThan(0); // Execute permissions
- } else {
- // Skip Linux-specific tests on other platforms
- expect(currentPlatform).not.toBe('linux');
- }
- });
- });
- describe('File Dialog Integration', () => {
- it('should provide platform-appropriate file dialog options', () => {
- const getFileDialogOptions = (type) => {
- const baseOptions = {
- title: 'Select File',
- buttonLabel: 'Select'
- };
- switch (type) {
- case 'save':
- return {
- ...baseOptions,
- defaultPath: path.join(os.homedir(), 'Downloads'),
- filters: [
- { name: 'Video Files', extensions: ['mp4', 'mkv', 'avi'] },
- { name: 'All Files', extensions: ['*'] }
- ]
- };
- case 'cookie':
- return {
- ...baseOptions,
- filters: [
- { name: 'Text Files', extensions: ['txt'] },
- { name: 'All Files', extensions: ['*'] }
- ]
- };
- default:
- return baseOptions;
- }
- };
- const saveOptions = getFileDialogOptions('save');
- const cookieOptions = getFileDialogOptions('cookie');
- expect(saveOptions.defaultPath).toContain(os.homedir());
- expect(saveOptions.filters).toHaveLength(2);
- expect(cookieOptions.filters).toHaveLength(2);
- });
- it('should handle default download directories per platform', () => {
- const getDefaultDownloadPath = () => {
- const homeDir = os.homedir();
-
- switch (currentPlatform) {
- case 'win32':
- return path.join(homeDir, 'Downloads');
- case 'darwin':
- return path.join(homeDir, 'Downloads');
- case 'linux':
- return path.join(homeDir, 'Downloads');
- default:
- return homeDir;
- }
- };
- const downloadPath = getDefaultDownloadPath();
- expect(path.isAbsolute(downloadPath)).toBe(true);
- expect(downloadPath).toContain(os.homedir());
- });
- });
- describe('Notification System', () => {
- it('should provide platform-appropriate notification options', () => {
- const getNotificationOptions = (title, body) => {
- const baseOptions = {
- title,
- body,
- silent: false
- };
- switch (currentPlatform) {
- case 'win32':
- return {
- ...baseOptions,
- toastXml: null // Windows-specific toast XML
- };
- case 'darwin':
- return {
- ...baseOptions,
- sound: 'default' // macOS notification sound
- };
- case 'linux':
- return {
- ...baseOptions,
- urgency: 'normal' // Linux notification urgency
- };
- default:
- return baseOptions;
- }
- };
- const options = getNotificationOptions('Test', 'Test notification');
- expect(options.title).toBe('Test');
- expect(options.body).toBe('Test notification');
-
- // Platform-specific properties
- if (currentPlatform === 'win32') {
- expect(options).toHaveProperty('toastXml');
- } else if (currentPlatform === 'darwin') {
- expect(options).toHaveProperty('sound');
- } else if (currentPlatform === 'linux') {
- expect(options).toHaveProperty('urgency');
- }
- });
- });
- describe('Performance Characteristics', () => {
- it('should account for platform-specific performance differences', () => {
- const performanceMetrics = {
- startupTime: 0,
- memoryUsage: process.memoryUsage(),
- cpuUsage: process.cpuUsage()
- };
- // Simulate startup time measurement
- const startTime = Date.now();
- // Simulate some work
- for (let i = 0; i < 1000000; i++) {
- Math.random();
- }
- performanceMetrics.startupTime = Date.now() - startTime;
- expect(performanceMetrics.startupTime).toBeGreaterThan(0);
- expect(performanceMetrics.memoryUsage.heapUsed).toBeGreaterThan(0);
- expect(performanceMetrics.cpuUsage.user).toBeGreaterThanOrEqual(0);
- });
- it('should handle concurrent operations efficiently per platform', () => {
- const maxConcurrency = currentPlatform === 'win32' ? 2 : 3; // Windows might be more conservative
-
- expect(maxConcurrency).toBeGreaterThan(0);
- expect(maxConcurrency).toBeLessThanOrEqual(4);
- });
- });
- describe('Error Handling and Recovery', () => {
- it('should provide platform-specific error messages', () => {
- const getErrorMessage = (errorType) => {
- const messages = {
- 'file_not_found': {
- 'win32': 'The system cannot find the file specified.',
- 'darwin': 'No such file or directory',
- 'linux': 'No such file or directory'
- },
- 'permission_denied': {
- 'win32': 'Access is denied.',
- 'darwin': 'Permission denied',
- 'linux': 'Permission denied'
- }
- };
- return messages[errorType]?.[currentPlatform] || 'Unknown error';
- };
- const fileNotFoundMsg = getErrorMessage('file_not_found');
- const permissionDeniedMsg = getErrorMessage('permission_denied');
- expect(fileNotFoundMsg).toBeTruthy();
- expect(permissionDeniedMsg).toBeTruthy();
-
- if (currentPlatform === 'win32') {
- expect(fileNotFoundMsg).toContain('system cannot find');
- expect(permissionDeniedMsg).toContain('Access is denied');
- } else {
- expect(fileNotFoundMsg).toContain('No such file');
- expect(permissionDeniedMsg).toContain('Permission denied');
- }
- });
- it('should handle platform-specific recovery strategies', () => {
- const getRecoveryStrategy = (errorType) => {
- switch (errorType) {
- case 'binary_not_found':
- return currentPlatform === 'win32'
- ? 'Check PATH environment variable and .exe extension'
- : 'Check PATH and executable permissions';
- case 'network_error':
- return 'Check internet connection and firewall settings';
- default:
- return 'Try restarting the application';
- }
- };
- const binaryStrategy = getRecoveryStrategy('binary_not_found');
- const networkStrategy = getRecoveryStrategy('network_error');
- expect(binaryStrategy).toBeTruthy();
- expect(networkStrategy).toBeTruthy();
-
- if (currentPlatform === 'win32') {
- expect(binaryStrategy).toContain('.exe');
- } else {
- expect(binaryStrategy).toContain('permissions');
- }
- });
- });
- });
|