Skip to main content
Glama
cross-platform.test.ts19.2 kB
import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals'; import { checkFFmpegAvailability, getFFmpegVersion } from '../../src/utils/ffmpeg.js'; import { validateFilePath } from '../../src/schemas/validation.js'; import AudioProcessor from '../../src/services/audio-processor.js'; // Mock dependencies jest.mock('which'); jest.mock('fluent-ffmpeg'); jest.mock('fs', () => ({ promises: { stat: jest.fn(), access: jest.fn(), mkdir: jest.fn(), unlink: jest.fn() }, constants: { R_OK: 4, W_OK: 2, F_OK: 0 } })); const mockWhich = jest.mocked(await import('which')).default; const mockFfmpeg = jest.mocked(await import('fluent-ffmpeg')).default; const mockFs = jest.mocked((await import('fs')).promises); describe('Cross-Platform Compatibility Tests', () => { let audioProcessor: AudioProcessor; let originalPlatform: string; beforeEach(() => { audioProcessor = new AudioProcessor(); originalPlatform = process.platform; jest.clearAllMocks(); }); afterEach(() => { // Restore original platform Object.defineProperty(process, 'platform', { value: originalPlatform, writable: true }); jest.clearAllMocks(); }); describe('Platform Detection and Path Handling', () => { it('should handle Windows-style paths correctly', () => { Object.defineProperty(process, 'platform', { value: 'win32', writable: true }); const windowsPaths = [ 'C:\\Users\\user\\audio\\music.mp3', 'D:\\Projects\\game\\sounds\\effect.wav', '\\\\server\\share\\audio.flac', 'audio.mp3', // Relative path '.\\sounds\\voice.aac' ]; windowsPaths.forEach(path => { expect(validateFilePath(path)).toBe(true); }); }); it('should handle Unix-style paths correctly', () => { Object.defineProperty(process, 'platform', { value: 'linux', writable: true }); const unixPaths = [ '/home/user/audio/music.mp3', '/usr/share/sounds/effect.wav', './sounds/voice.aac', '../audio/track.flac', 'audio.mp3' // Relative path ]; unixPaths.forEach(path => { expect(validateFilePath(path)).toBe(true); }); }); it('should handle macOS-specific paths correctly', () => { Object.defineProperty(process, 'platform', { value: 'darwin', writable: true }); const macosPaths = [ '/Users/user/Music/song.mp3', '/System/Library/Sounds/effect.wav', '/Volumes/ExternalDrive/audio.flac', '~/Documents/audio/voice.aac' ]; macosPaths.forEach(path => { expect(validateFilePath(path)).toBe(true); }); }); }); describe('FFmpeg Detection Across Platforms', () => { it('should detect FFmpeg on Windows', async () => { Object.defineProperty(process, 'platform', { value: 'win32', writable: true }); // Mock Windows FFmpeg paths const windowsFFmpegPaths = [ 'C:\\Program Files\\FFmpeg\\bin\\ffmpeg.exe', 'C:\\ffmpeg\\bin\\ffmpeg.exe', 'ffmpeg.exe' ]; for (const ffmpegPath of windowsFFmpegPaths) { mockWhich.mockResolvedValue(ffmpegPath); const available = await checkFFmpegAvailability(); expect(available).toBe(true); expect(mockWhich).toHaveBeenCalledWith('ffmpeg'); } }); it('should detect FFmpeg on Linux', async () => { Object.defineProperty(process, 'platform', { value: 'linux', writable: true }); // Mock Linux FFmpeg paths const linuxFFmpegPaths = [ '/usr/bin/ffmpeg', '/usr/local/bin/ffmpeg', '/snap/bin/ffmpeg', '/opt/ffmpeg/bin/ffmpeg' ]; for (const ffmpegPath of linuxFFmpegPaths) { jest.clearAllMocks(); mockWhich.mockResolvedValue(ffmpegPath); const available = await checkFFmpegAvailability(); expect(available).toBe(true); } }); it('should detect FFmpeg on macOS', async () => { Object.defineProperty(process, 'platform', { value: 'darwin', writable: true }); // Mock macOS FFmpeg paths const macosFFmpegPaths = [ '/usr/local/bin/ffmpeg', '/opt/homebrew/bin/ffmpeg', '/usr/bin/ffmpeg' ]; for (const ffmpegPath of macosFFmpegPaths) { jest.clearAllMocks(); mockWhich.mockResolvedValue(ffmpegPath); const available = await checkFFmpegAvailability(); expect(available).toBe(true); } }); it('should handle FFmpeg not found on any platform', async () => { const platforms = ['win32', 'linux', 'darwin']; for (const platform of platforms) { Object.defineProperty(process, 'platform', { value: platform, writable: true }); jest.clearAllMocks(); mockWhich.mockRejectedValue(new Error('Command not found')); const available = await checkFFmpegAvailability(); expect(available).toBe(false); } }); }); describe('File System Operations Across Platforms', () => { it('should handle Windows file permissions correctly', async () => { Object.defineProperty(process, 'platform', { value: 'win32', writable: true }); // Mock Windows file stats mockFs.stat.mockResolvedValue({ isFile: () => true, mode: 0o644 // rw-r--r-- } as any); mockFs.access.mockResolvedValue(undefined); const { validateInputFile } = await import('../../src/utils/ffmpeg.js'); await expect(validateInputFile('C:\\audio\\test.mp3')).resolves.toBeUndefined(); expect(mockFs.access).toHaveBeenCalledWith('C:\\audio\\test.mp3', mockFs.constants.R_OK); }); it('should handle Unix file permissions correctly', async () => { Object.defineProperty(process, 'platform', { value: 'linux', writable: true }); // Mock Unix file stats with executable permissions mockFs.stat.mockResolvedValue({ isFile: () => true, mode: 0o755 // rwxr-xr-x } as any); mockFs.access.mockResolvedValue(undefined); const { validateInputFile } = await import('../../src/utils/ffmpeg.js'); await expect(validateInputFile('/home/user/audio/test.mp3')).resolves.toBeUndefined(); }); it('should handle permission denied errors appropriately', async () => { const platforms = ['win32', 'linux', 'darwin']; for (const platform of platforms) { Object.defineProperty(process, 'platform', { value: platform, writable: true }); jest.clearAllMocks(); mockFs.stat.mockResolvedValue({ isFile: () => true } as any); mockFs.access.mockRejectedValue(new Error('EACCES: permission denied')); const { validateInputFile } = await import('../../src/utils/ffmpeg.js'); await expect(validateInputFile('/restricted/test.mp3')) .rejects.toThrow('Cannot access input file'); } }); }); describe('Directory Creation Across Platforms', () => { it('should create directories with correct separators on Windows', async () => { Object.defineProperty(process, 'platform', { value: 'win32', writable: true }); mockFs.mkdir.mockResolvedValue(undefined); mockFs.access.mockResolvedValue(undefined); const { ensureOutputDirectory } = await import('../../src/utils/ffmpeg.js'); await ensureOutputDirectory('C:\\output\\processed\\audio.mp3'); expect(mockFs.mkdir).toHaveBeenCalledWith('C:\\output\\processed', { recursive: true }); }); it('should create directories with correct separators on Unix', async () => { Object.defineProperty(process, 'platform', { value: 'linux', writable: true }); mockFs.mkdir.mockResolvedValue(undefined); mockFs.access.mockResolvedValue(undefined); const { ensureOutputDirectory } = await import('../../src/utils/ffmpeg.js'); await ensureOutputDirectory('/output/processed/audio.mp3'); expect(mockFs.mkdir).toHaveBeenCalledWith('/output/processed', { recursive: true }); }); it('should handle deep directory structures on all platforms', async () => { const testCases = [ { platform: 'win32', path: 'C:\\very\\deep\\nested\\directory\\structure\\file.mp3' }, { platform: 'linux', path: '/very/deep/nested/directory/structure/file.mp3' }, { platform: 'darwin', path: '/Users/user/very/deep/nested/directory/structure/file.mp3' } ]; for (const testCase of testCases) { Object.defineProperty(process, 'platform', { value: testCase.platform, writable: true }); jest.clearAllMocks(); mockFs.mkdir.mockResolvedValue(undefined); mockFs.access.mockResolvedValue(undefined); const { ensureOutputDirectory } = await import('../../src/utils/ffmpeg.js'); await expect(ensureOutputDirectory(testCase.path)).resolves.toBeUndefined(); expect(mockFs.mkdir).toHaveBeenCalledWith( expect.stringContaining('structure'), { recursive: true } ); } }); }); describe('Command Execution Compatibility', () => { let mockCommand: any; beforeEach(() => { mockCommand = { audioFilters: jest.fn().mockReturnThis(), audioFrequency: jest.fn().mockReturnThis(), audioChannels: jest.fn().mockReturnThis(), audioCodec: jest.fn().mockReturnThis(), audioBitrate: jest.fn().mockReturnThis(), seekInput: jest.fn().mockReturnThis(), duration: jest.fn().mockReturnThis(), output: jest.fn().mockReturnThis(), on: jest.fn().mockReturnThis(), run: jest.fn() }; (mockFfmpeg as any).mockReturnValue(mockCommand); }); it('should execute FFmpeg commands on Windows', async () => { Object.defineProperty(process, 'platform', { value: 'win32', writable: true }); mockFs.stat.mockResolvedValue({ isFile: () => true } as any); mockFs.access.mockResolvedValue(undefined); mockFs.mkdir.mockResolvedValue(undefined); // Mock successful execution mockCommand.on.mockImplementation((event: string, callback: Function) => { if (event === 'end') { setTimeout(() => callback(), 0); } return mockCommand; }); const result = await audioProcessor.processAudioFile( 'C:\\input\\audio.wav', 'C:\\output\\processed.mp3', { format: { codec: 'mp3' } }, false ); expect(result.success).toBe(true); expect(mockCommand.run).toHaveBeenCalled(); }); it('should execute FFmpeg commands on Linux', async () => { Object.defineProperty(process, 'platform', { value: 'linux', writable: true }); mockFs.stat.mockResolvedValue({ isFile: () => true } as any); mockFs.access.mockResolvedValue(undefined); mockFs.mkdir.mockResolvedValue(undefined); mockCommand.on.mockImplementation((event: string, callback: Function) => { if (event === 'end') { setTimeout(() => callback(), 0); } return mockCommand; }); const result = await audioProcessor.processAudioFile( '/input/audio.wav', '/output/processed.mp3', { format: { codec: 'mp3' } }, false ); expect(result.success).toBe(true); expect(mockCommand.run).toHaveBeenCalled(); }); it('should execute FFmpeg commands on macOS', async () => { Object.defineProperty(process, 'platform', { value: 'darwin', writable: true }); mockFs.stat.mockResolvedValue({ isFile: () => true } as any); mockFs.access.mockResolvedValue(undefined); mockFs.mkdir.mockResolvedValue(undefined); mockCommand.on.mockImplementation((event: string, callback: Function) => { if (event === 'end') { setTimeout(() => callback(), 0); } return mockCommand; }); const result = await audioProcessor.processAudioFile( '/Users/user/input/audio.wav', '/Users/user/output/processed.mp3', { format: { codec: 'mp3' } }, false ); expect(result.success).toBe(true); expect(mockCommand.run).toHaveBeenCalled(); }); }); describe('Environment Variable Handling', () => { let originalEnv: NodeJS.ProcessEnv; beforeEach(() => { originalEnv = { ...process.env }; }); afterEach(() => { process.env = originalEnv; }); it('should handle Windows environment variables', () => { Object.defineProperty(process, 'platform', { value: 'win32', writable: true }); // Simulate Windows environment process.env.USERPROFILE = 'C:\\Users\\TestUser'; process.env.APPDATA = 'C:\\Users\\TestUser\\AppData\\Roaming'; process.env.PATH = 'C:\\Windows\\System32;C:\\Program Files\\FFmpeg\\bin'; // Test that the package can handle Windows-style environment variables expect(process.env.USERPROFILE).toBe('C:\\Users\\TestUser'); expect(process.env.PATH).toContain('FFmpeg'); }); it('should handle Unix environment variables', () => { Object.defineProperty(process, 'platform', { value: 'linux', writable: true }); // Simulate Unix environment process.env.HOME = '/home/testuser'; process.env.PATH = '/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin'; expect(process.env.HOME).toBe('/home/testuser'); expect(process.env.PATH).toContain('/usr/bin'); }); it('should handle macOS environment variables', () => { Object.defineProperty(process, 'platform', { value: 'darwin', writable: true }); // Simulate macOS environment process.env.HOME = '/Users/testuser'; process.env.PATH = '/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin'; expect(process.env.HOME).toBe('/Users/testuser'); expect(process.env.PATH).toContain('/opt/homebrew/bin'); }); }); describe('Temporary File Handling', () => { it('should handle temporary files correctly on Windows', async () => { Object.defineProperty(process, 'platform', { value: 'win32', writable: true }); const tempFiles = [ 'C:\\Users\\TestUser\\AppData\\Local\\Temp\\audio_temp_1.wav', 'C:\\Users\\TestUser\\AppData\\Local\\Temp\\audio_temp_2.mp3' ]; mockFs.unlink.mockResolvedValue(undefined); const { cleanupTempFiles } = await import('../../src/utils/ffmpeg.js'); await expect(cleanupTempFiles(tempFiles)).resolves.toBeUndefined(); expect(mockFs.unlink).toHaveBeenCalledTimes(2); tempFiles.forEach(file => { expect(mockFs.unlink).toHaveBeenCalledWith(file); }); }); it('should handle temporary files correctly on Unix systems', async () => { Object.defineProperty(process, 'platform', { value: 'linux', writable: true }); const tempFiles = [ '/tmp/audio_temp_1.wav', '/tmp/audio_temp_2.mp3', '/var/tmp/audio_temp_3.ogg' ]; mockFs.unlink.mockResolvedValue(undefined); const { cleanupTempFiles } = await import('../../src/utils/ffmpeg.js'); await expect(cleanupTempFiles(tempFiles)).resolves.toBeUndefined(); expect(mockFs.unlink).toHaveBeenCalledTimes(3); }); }); describe('Error Message Compatibility', () => { it('should provide platform-appropriate error messages', async () => { const platforms = [ { name: 'win32', expectedPath: 'C:\\nonexistent\\file.mp3' }, { name: 'linux', expectedPath: '/nonexistent/file.mp3' }, { name: 'darwin', expectedPath: '/Users/nonexistent/file.mp3' } ]; for (const platform of platforms) { Object.defineProperty(process, 'platform', { value: platform.name, writable: true }); jest.clearAllMocks(); mockFs.stat.mockRejectedValue(new Error('ENOENT: no such file or directory')); const { validateInputFile } = await import('../../src/utils/ffmpeg.js'); await expect(validateInputFile(platform.expectedPath)) .rejects.toThrow('Cannot access input file'); } }); }); describe('Performance Across Platforms', () => { it('should perform consistently across platforms', async () => { const platforms = ['win32', 'linux', 'darwin']; const results: Array<{ platform: string; time: number }> = []; for (const platform of platforms) { Object.defineProperty(process, 'platform', { value: platform, writable: true }); // Setup successful mocks mockFs.stat.mockResolvedValue({ isFile: () => true } as any); mockFs.access.mockResolvedValue(undefined); mockFs.mkdir.mockResolvedValue(undefined); mockCommand.on.mockImplementation((event: string, callback: Function) => { if (event === 'end') { setTimeout(() => callback(), 50); // Fixed processing time } return mockCommand; }); const startTime = Date.now(); const result = await audioProcessor.processAudioFile( '/input/test.wav', '/output/test.mp3', { format: { codec: 'mp3' } }, false ); const endTime = Date.now(); expect(result.success).toBe(true); results.push({ platform, time: endTime - startTime }); jest.clearAllMocks(); } // Performance should be consistent across platforms (within reasonable variance) const times = results.map(r => r.time); const maxTime = Math.max(...times); const minTime = Math.min(...times); const variance = maxTime - minTime; expect(variance).toBeLessThan(100); // Should be within 100ms of each other console.log('Cross-platform performance:'); results.forEach(r => { console.log(` ${r.platform}: ${r.time}ms`); }); }); }); describe('Node.js Version Compatibility', () => { it('should work with different Node.js versions', () => { const nodeVersions = ['18.0.0', '20.0.0', '22.0.0']; const originalVersion = process.version; nodeVersions.forEach(version => { Object.defineProperty(process, 'version', { value: `v${version}`, writable: true }); // Test that the package imports work with different Node versions expect(() => { require('../../src/index.js'); }).not.toThrow(); }); // Restore original version Object.defineProperty(process, 'version', { value: originalVersion, writable: true }); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/DeveloperZo/mcp-audio-tweaker'

If you have feedback or need assistance with the MCP directory API, please join our Discord server